From aac0daefea97217619ceb41370b674d9f8f3f941 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Mon, 6 Jan 2025 13:20:57 +0100 Subject: [PATCH] more cleaning on graphs --- examples/cpp/BUILD.bazel | 4 + examples/cpp/CMakeLists.txt | 3 +- examples/cpp/fap_model_printer.cc | 93 +++ examples/cpp/fap_model_printer.h | 73 +- examples/cpp/fap_parser.cc | 386 +++++++++ examples/cpp/fap_parser.h | 392 +-------- examples/cpp/fap_utilities.cc | 185 +++++ examples/cpp/fap_utilities.h | 177 +--- examples/cpp/max_flow.cc | 2 +- examples/cpp/mps_driver.cc | 6 +- examples/cpp/network_routing_sat.cc | 9 +- examples/cpp/parse_dimacs_assignment.cc | 19 + examples/cpp/parse_dimacs_assignment.h | 9 +- ortools/graph/BUILD.bazel | 9 +- ortools/graph/assignment.cc | 28 +- ortools/graph/ebert_graph.h | 426 +--------- ortools/graph/ebert_graph_test.cc | 5 +- ortools/graph/generic_max_flow.h | 13 - ortools/graph/linear_assignment_test.cc | 6 +- ortools/graph/min_cost_flow.cc | 83 +- .../samples/simple_min_cost_flow_program.cc | 1 + ortools/graph/shortest_paths.h | 754 +++++++++++++++--- ortools/graph/shortest_paths_benchmarks.cc | 12 +- ortools/graph/shortest_paths_test.cc | 89 ++- ortools/routing/routing.cc | 20 +- 25 files changed, 1511 insertions(+), 1293 deletions(-) create mode 100644 examples/cpp/fap_model_printer.cc create mode 100644 examples/cpp/fap_parser.cc create mode 100644 examples/cpp/fap_utilities.cc create mode 100644 examples/cpp/parse_dimacs_assignment.cc diff --git a/examples/cpp/BUILD.bazel b/examples/cpp/BUILD.bazel index c36be4c028b..1b380f4ddac 100644 --- a/examples/cpp/BUILD.bazel +++ b/examples/cpp/BUILD.bazel @@ -724,6 +724,7 @@ cc_library( cc_library( name = "parse_dimacs_assignment", + srcs = ["parse_dimacs_assignment.cc"], hdrs = ["parse_dimacs_assignment.h"], deps = [ "//ortools/base", @@ -877,6 +878,7 @@ cc_test( # Frequency Assignment Problem cc_library( name = "fap_parser", + srcs = ["fap_parser.cc"], hdrs = ["fap_parser.h"], deps = [ "//ortools/base", @@ -890,6 +892,7 @@ cc_library( cc_library( name = "fap_model_printer", + srcs = ["fap_model_printer.cc"], hdrs = ["fap_model_printer.h"], deps = [ ":fap_parser", @@ -902,6 +905,7 @@ cc_library( cc_library( name = "fap_utilities", + srcs = ["fap_utilities.cc"], hdrs = ["fap_utilities.h"], deps = [ ":fap_parser", diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 7fe5b30fcda..bfced325136 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -44,16 +44,17 @@ list(FILTER CXX_SRCS EXCLUDE REGEX ".*/course_scheduling_run.cc") # missing prot list(FILTER CXX_SRCS EXCLUDE REGEX ".*/course_scheduling.cc") # missing proto list(FILTER CXX_SRCS EXCLUDE REGEX ".*/dimacs_assignment.cc") # crash list(FILTER CXX_SRCS EXCLUDE REGEX ".*/dobble_ls.cc") # Too long +list(FILTER CXX_SRCS EXCLUDE REGEX ".*/fap_*.cc") # crash list(FILTER CXX_SRCS EXCLUDE REGEX ".*/frequency_assignment_problem.cc") # crash list(FILTER CXX_SRCS EXCLUDE REGEX ".*/jobshop_sat.cc") # crash list(FILTER CXX_SRCS EXCLUDE REGEX ".*/knapsack_2d_sat.cc") list(FILTER CXX_SRCS EXCLUDE REGEX ".*/mps_driver.cc") # crash list(FILTER CXX_SRCS EXCLUDE REGEX ".*/multi_knapsack_sat.cc") # crash list(FILTER CXX_SRCS EXCLUDE REGEX ".*/network_routing_sat.cc") +list(FILTER CXX_SRCS EXCLUDE REGEX ".*/parse_dimacs_assignment.*") list(FILTER CXX_SRCS EXCLUDE REGEX ".*/pdlp_solve.cc") list(FILTER CXX_SRCS EXCLUDE REGEX ".*/pdptw.cc") list(FILTER CXX_SRCS EXCLUDE REGEX ".*/shift_minimization_sat.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/pdlp_solve.cc") list(FILTER CXX_SRCS EXCLUDE REGEX ".*/strawberry_fields_with_column_generation.cc") # Too long list(FILTER CXX_SRCS EXCLUDE REGEX ".*/vector_bin_packing_solver.cc") list(FILTER CXX_SRCS EXCLUDE REGEX ".*/weighted_tardiness_sat.cc") diff --git a/examples/cpp/fap_model_printer.cc b/examples/cpp/fap_model_printer.cc new file mode 100644 index 00000000000..7144563d6fd --- /dev/null +++ b/examples/cpp/fap_model_printer.cc @@ -0,0 +1,93 @@ +// Copyright 2010-2024 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 "examples/cpp/fap_model_printer.h" + +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "ortools/base/logging.h" + +namespace operations_research { + +FapModelPrinter::FapModelPrinter( + const absl::btree_map& variables, + const std::vector& constraints, absl::string_view objective, + const std::vector& values) + : variables_(variables), + constraints_(constraints), + objective_(objective), + values_(values) {} + +FapModelPrinter::~FapModelPrinter() = default; + +void FapModelPrinter::PrintFapVariables() { + LOG(INFO) << "Variable File:"; + for (const auto& it : variables_) { + std::string domain = "{"; + for (const int value : it.second.domain) { + absl::StrAppendFormat(&domain, "%d ", value); + } + domain.append("}"); + + std::string hard = " "; + if (it.second.hard) { + hard = " hard"; + } + + LOG(INFO) << "Variable " << absl::StrFormat("%3d: ", it.first) + << absl::StrFormat("(degree: %2d) ", it.second.degree) + << absl::StrFormat("%3d", it.second.domain_index) + << absl::StrFormat("%3d", it.second.initial_position) + << absl::StrFormat("%3d", it.second.mobility_index) + << absl::StrFormat("%8d", it.second.mobility_cost) + << absl::StrFormat(" (%2d) ", it.second.domain_size) << domain + << hard; + } +} + +void FapModelPrinter::PrintFapConstraints() { + LOG(INFO) << "Constraint File:"; + for (const FapConstraint& ct : constraints_) { + std::string hard = " "; + if (ct.hard) { + hard = " hard"; + } + + LOG(INFO) << absl::StrFormat("%3d ", ct.variable1) + << absl::StrFormat("%3d ", ct.variable2) << ct.type << " " + << ct.operation << " " << absl::StrFormat("%3d", ct.value) + << absl::StrFormat("%3d", ct.weight_index) + << absl::StrFormat("%8d", ct.weight_cost) << hard; + } +} + +void FapModelPrinter::PrintFapObjective() { + LOG(INFO) << "Objective: " << objective_; +} + +void FapModelPrinter::PrintFapValues() { + LOG(INFO) << absl::StrFormat("Values(%d): ", + static_cast(values_.size())); + std::string domain = " "; + for (const int value : values_) { + absl::StrAppendFormat(&domain, "%d ", value); + } + LOG(INFO) << domain; +} + +} // namespace operations_research diff --git a/examples/cpp/fap_model_printer.h b/examples/cpp/fap_model_printer.h index 14fd3955ac4..f043e176679 100644 --- a/examples/cpp/fap_model_printer.h +++ b/examples/cpp/fap_model_printer.h @@ -19,12 +19,10 @@ #ifndef OR_TOOLS_EXAMPLES_FAP_MODEL_PRINTER_H_ #define OR_TOOLS_EXAMPLES_FAP_MODEL_PRINTER_H_ -#include #include #include #include "absl/container/btree_map.h" -#include "absl/strings/str_format.h" #include "examples/cpp/fap_parser.h" namespace operations_research { @@ -35,6 +33,11 @@ class FapModelPrinter { FapModelPrinter(const absl::btree_map& variables, const std::vector& constraints, absl::string_view objective, const std::vector& values); + + // This type is neither copyable nor movable. + FapModelPrinter(const FapModelPrinter&) = delete; + FapModelPrinter& operator=(const FapModelPrinter&) = delete; + ~FapModelPrinter(); void PrintFapObjective(); @@ -47,73 +50,7 @@ class FapModelPrinter { const std::vector constraints_; const std::string objective_; const std::vector values_; - DISALLOW_COPY_AND_ASSIGN(FapModelPrinter); }; -FapModelPrinter::FapModelPrinter(const absl::btree_map& variables, - const std::vector& constraints, - absl::string_view objective, - const std::vector& values) - : variables_(variables), - constraints_(constraints), - objective_(objective), - values_(values) {} - -FapModelPrinter::~FapModelPrinter() {} - -void FapModelPrinter::PrintFapVariables() { - LOG(INFO) << "Variable File:"; - for (const auto& it : variables_) { - std::string domain = "{"; - for (const int value : it.second.domain) { - absl::StrAppendFormat(&domain, "%d ", value); - } - domain.append("}"); - - std::string hard = " "; - if (it.second.hard) { - hard = " hard"; - } - - LOG(INFO) << "Variable " << absl::StrFormat("%3d: ", it.first) - << absl::StrFormat("(degree: %2d) ", it.second.degree) - << absl::StrFormat("%3d", it.second.domain_index) - << absl::StrFormat("%3d", it.second.initial_position) - << absl::StrFormat("%3d", it.second.mobility_index) - << absl::StrFormat("%8d", it.second.mobility_cost) - << absl::StrFormat(" (%2d) ", it.second.domain_size) << domain - << hard; - } -} - -void FapModelPrinter::PrintFapConstraints() { - LOG(INFO) << "Constraint File:"; - for (const FapConstraint& ct : constraints_) { - std::string hard = " "; - if (ct.hard) { - hard = " hard"; - } - - LOG(INFO) << absl::StrFormat("%3d ", ct.variable1) - << absl::StrFormat("%3d ", ct.variable2) << ct.type << " " - << ct.operation << " " << absl::StrFormat("%3d", ct.value) - << absl::StrFormat("%3d", ct.weight_index) - << absl::StrFormat("%8d", ct.weight_cost) << hard; - } -} - -void FapModelPrinter::PrintFapObjective() { - LOG(INFO) << "Objective: " << objective_; -} - -void FapModelPrinter::PrintFapValues() { - LOG(INFO) << absl::StrFormat("Values(%d): ", values_.size()); - std::string domain = " "; - for (const int value : values_) { - absl::StrAppendFormat(&domain, "%d ", value); - } - LOG(INFO) << domain; -} - } // namespace operations_research #endif // OR_TOOLS_EXAMPLES_FAP_MODEL_PRINTER_H_ diff --git a/examples/cpp/fap_parser.cc b/examples/cpp/fap_parser.cc new file mode 100644 index 00000000000..900ee9864dd --- /dev/null +++ b/examples/cpp/fap_parser.cc @@ -0,0 +1,386 @@ +// Copyright 2010-2024 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 "examples/cpp/fap_parser.h" + +#include +#include +#include + +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" +#include "ortools/base/helpers.h" +#include "ortools/base/map_util.h" + +namespace operations_research { +namespace { +int strtoint32(const std::string& word) { + int result; + CHECK(absl::SimpleAtoi(word, &result)); + return result; +} +} // namespace + +void ParseFileByLines(const std::string& filename, + std::vector* lines) { + CHECK(lines != nullptr); + std::string result; + CHECK_OK(file::GetContents(filename, &result, file::Defaults())); + *lines = absl::StrSplit(result, '\n', absl::SkipEmpty()); +} + +// VariableParser Implementation +VariableParser::VariableParser(const std::string& data_directory) + : filename_(data_directory + "/var.txt") {} + +VariableParser::~VariableParser() = default; + +void VariableParser::Parse() { + std::vector lines; + ParseFileByLines(filename_, &lines); + for (const std::string& line : lines) { + std::vector tokens = + absl::StrSplit(line, ' ', absl::SkipEmpty()); + if (tokens.empty()) { + continue; + } + CHECK_GE(tokens.size(), 2); + + FapVariable variable; + variable.domain_index = strtoint32(tokens[1]); + if (tokens.size() > 3) { + variable.initial_position = strtoint32(tokens[2]); + variable.mobility_index = strtoint32(tokens[3]); + } + gtl::InsertOrUpdate(&variables_, strtoint32(tokens[0]), variable); + } +} + +// DomainParser Implementation +DomainParser::DomainParser(const std::string& data_directory) + : filename_(data_directory + "/dom.txt") {} + +DomainParser::~DomainParser() = default; + +void DomainParser::Parse() { + std::vector lines; + ParseFileByLines(filename_, &lines); + for (const std::string& line : lines) { + std::vector tokens = + absl::StrSplit(line, ' ', absl::SkipEmpty()); + if (tokens.empty()) { + continue; + } + CHECK_GE(tokens.size(), 2); + + const int key = strtoint32(tokens[0]); + + std::vector domain; + domain.clear(); + for (int i = 2; i < tokens.size(); ++i) { + domain.push_back(strtoint32(tokens[i])); + } + + if (!domain.empty()) { + gtl::InsertOrUpdate(&domains_, key, domain); + } + } +} + +// ConstraintParser Implementation +ConstraintParser::ConstraintParser(const std::string& data_directory) + : filename_(data_directory + "/ctr.txt") {} + +ConstraintParser::~ConstraintParser() = default; + +void ConstraintParser::Parse() { + std::vector lines; + ParseFileByLines(filename_, &lines); + for (const std::string& line : lines) { + std::vector tokens = + absl::StrSplit(line, ' ', absl::SkipEmpty()); + if (tokens.empty()) { + continue; + } + CHECK_GE(tokens.size(), 5); + + FapConstraint constraint; + constraint.variable1 = strtoint32(tokens[0]); + constraint.variable2 = strtoint32(tokens[1]); + constraint.type = tokens[2]; + constraint.operation = tokens[3]; + constraint.value = strtoint32(tokens[4]); + + if (tokens.size() > 5) { + constraint.weight_index = strtoint32(tokens[5]); + } + constraints_.push_back(constraint); + } +} + +// ParametersParser Implementation +const int ParametersParser::kConstraintCoefficientNo; +const int ParametersParser::kVariableCoefficientNo; +const int ParametersParser::kCoefficientNo; + +ParametersParser::ParametersParser(const std::string& data_directory) + : filename_(data_directory + "/cst.txt"), + objective_(""), + constraint_weights_(kConstraintCoefficientNo, 0), + variable_weights_(kVariableCoefficientNo, 0) {} + +ParametersParser::~ParametersParser() = default; + +void ParametersParser::Parse() { + bool objective = true; + bool largest_token = false; + bool value_token = false; + bool number_token = false; + bool values_token = false; + bool coefficient = false; + std::vector coefficients; + std::vector lines; + + ParseFileByLines(filename_, &lines); + for (const std::string& line : lines) { + if (objective) { + largest_token = largest_token || absl::StrContains(line, "largest"); + value_token = value_token || absl::StrContains(line, "value"); + number_token = number_token || absl::StrContains(line, "number"); + values_token = values_token || absl::StrContains(line, "values"); + coefficient = coefficient || absl::StrContains(line, "coefficient"); + } + + if (coefficient) { + CHECK_EQ(kCoefficientNo, + kConstraintCoefficientNo + kVariableCoefficientNo); + objective = false; + if (absl::StrContains(line, "=")) { + std::vector tokens = + absl::StrSplit(line, ' ', absl::SkipEmpty()); + CHECK_GE(tokens.size(), 3); + coefficients.push_back(strtoint32(tokens[2])); + } + } + } + + if (coefficient) { + CHECK_EQ(kCoefficientNo, coefficients.size()); + for (int i = 0; i < kCoefficientNo; i++) { + if (i < kConstraintCoefficientNo) { + constraint_weights_[i] = coefficients[i]; + } else { + variable_weights_[i - kConstraintCoefficientNo] = coefficients[i]; + } + } + } + + if (largest_token && value_token) { + objective_ = "Minimize the largest assigned value."; + } else if (number_token && values_token) { + objective_ = "Minimize the number of assigned values."; + } else { + // Should not reach this point. + LOG(WARNING) << "Cannot read the objective of the instance."; + } +} + +// TODO(user): Make FindComponents linear instead of quadratic. +void FindComponents(const std::vector& constraints, + const absl::btree_map& variables, + const int maximum_variable_id, + absl::flat_hash_map* components) { + std::vector in_component(maximum_variable_id + 1, -1); + int constraint_index = 0; + for (const FapConstraint& constraint : constraints) { + const int variable_id1 = constraint.variable1; + const int variable_id2 = constraint.variable2; + const FapVariable& variable1 = gtl::FindOrDie(variables, variable_id1); + const FapVariable& variable2 = gtl::FindOrDie(variables, variable_id2); + CHECK_LT(variable_id1, in_component.size()); + CHECK_LT(variable_id2, in_component.size()); + if (in_component[variable_id1] < 0 && in_component[variable_id2] < 0) { + // None of the variables belong to an existing component. + // Create a new one. + FapComponent component; + const int component_index = constraint_index; + gtl::InsertOrUpdate(&(component.variables), variable_id1, variable1); + gtl::InsertOrUpdate(&(component.variables), variable_id2, variable2); + in_component[variable_id1] = component_index; + in_component[variable_id2] = component_index; + component.constraints.push_back(constraint); + gtl::InsertOrUpdate(components, component_index, component); + } else if (in_component[variable_id1] >= 0 && + in_component[variable_id2] < 0) { + // If variable1 belongs to an existing component, variable2 should + // also be included in the same component. + const int component_index = in_component[variable_id1]; + CHECK(components->contains(component_index)); + gtl::InsertOrUpdate(&((*components)[component_index].variables), + variable_id2, variable2); + in_component[variable_id2] = component_index; + (*components)[component_index].constraints.push_back(constraint); + } else if (in_component[variable_id1] < 0 && + in_component[variable_id2] >= 0) { + // If variable2 belongs to an existing component, variable1 should + // also be included in the same component. + const int component_index = in_component[variable_id2]; + CHECK(components->contains(component_index)); + gtl::InsertOrUpdate(&((*components)[component_index].variables), + variable_id1, variable1); + in_component[variable_id1] = component_index; + (*components)[component_index].constraints.push_back(constraint); + } else { + // The current constraint connects two different components. + const int component_index1 = in_component[variable_id1]; + const int component_index2 = in_component[variable_id2]; + const int min_component_index = + std::min(component_index1, component_index2); + const int max_component_index = + std::max(component_index1, component_index2); + CHECK(components->contains(min_component_index)); + CHECK(components->contains(max_component_index)); + if (min_component_index != max_component_index) { + // Update the component_index of maximum indexed component's variables. + for (const auto& variable : + (*components)[max_component_index].variables) { + int variable_id = variable.first; + in_component[variable_id] = min_component_index; + } + // Insert all the variables of the maximum indexed component to the + // variables of the minimum indexed component. + ((*components)[min_component_index]) + .variables.insert( + ((*components)[max_component_index]).variables.begin(), + ((*components)[max_component_index]).variables.end()); + // Insert all the constraints of the maximum indexed component to the + // constraints of the minimum indexed component. + ((*components)[min_component_index]) + .constraints.insert( + ((*components)[min_component_index]).constraints.end(), + ((*components)[max_component_index]).constraints.begin(), + ((*components)[max_component_index]).constraints.end()); + (*components)[min_component_index].constraints.push_back(constraint); + // Delete the maximum indexed component from the components set. + components->erase(max_component_index); + } else { + // Both variables belong to the same component, just add the constraint. + (*components)[min_component_index].constraints.push_back(constraint); + } + } + constraint_index++; + } +} + +int EvaluateConstraintImpact(const absl::btree_map& variables, + const int max_weight_cost, + const FapConstraint constraint) { + const FapVariable& variable1 = + gtl::FindOrDie(variables, constraint.variable1); + const FapVariable& variable2 = + gtl::FindOrDie(variables, constraint.variable2); + const int degree1 = variable1.degree; + const int degree2 = variable2.degree; + const int max_degree = std::max(degree1, degree2); + const int min_degree = std::min(degree1, degree2); + const int operator_impact = + constraint.operation == "=" ? max_degree : min_degree; + const int kHardnessBias = 10; + int hardness_impact = 0; + if (constraint.hard) { + hardness_impact = max_weight_cost > 0 ? kHardnessBias * max_weight_cost : 0; + } else { + hardness_impact = constraint.weight_cost; + } + return max_degree + min_degree + operator_impact + hardness_impact; +} + +void ParseInstance(const std::string& data_directory, bool find_components, + absl::btree_map* variables, + std::vector* constraints, + std::string* objective, std::vector* frequencies, + absl::flat_hash_map* components) { + CHECK(variables != nullptr); + CHECK(constraints != nullptr); + CHECK(objective != nullptr); + CHECK(frequencies != nullptr); + + // Parse the data files. + VariableParser var(data_directory); + var.Parse(); + *variables = var.variables(); + const int maximum_variable_id = variables->rbegin()->first; + + ConstraintParser ctr(data_directory); + ctr.Parse(); + *constraints = ctr.constraints(); + + DomainParser dom(data_directory); + dom.Parse(); + + ParametersParser cst(data_directory); + cst.Parse(); + const int maximum_weight_cost = *std::max_element( + (cst.constraint_weights()).begin(), (cst.constraint_weights()).end()); + + // Make the variables of the instance. + for (auto& it : *variables) { + it.second.domain = gtl::FindOrDie(dom.domains(), it.second.domain_index); + it.second.domain_size = it.second.domain.size(); + + if ((it.second.mobility_index == -1) || (it.second.mobility_index == 0)) { + it.second.mobility_cost = -1; + if (it.second.initial_position != -1) { + it.second.hard = true; + } + } else { + it.second.mobility_cost = + (cst.variable_weights())[it.second.mobility_index - 1]; + } + } + // Make the constraints of the instance. + for (FapConstraint& ct : *constraints) { + if ((ct.weight_index == -1) || (ct.weight_index == 0)) { + ct.weight_cost = -1; + ct.hard = true; + } else { + ct.weight_cost = (cst.constraint_weights())[ct.weight_index - 1]; + ct.hard = false; + } + ++((*variables)[ct.variable1]).degree; + ++((*variables)[ct.variable2]).degree; + } + // Make the available frequencies of the instance. + *frequencies = gtl::FindOrDie(dom.domains(), 0); + // Make the objective of the instance. + *objective = cst.objective(); + + if (find_components) { + CHECK(components != nullptr); + FindComponents(*constraints, *variables, maximum_variable_id, components); + // Evaluate each components's constraints impacts. + for (auto& component : *components) { + for (auto& constraint : component.second.constraints) { + constraint.impact = EvaluateConstraintImpact( + *variables, maximum_weight_cost, constraint); + } + } + } else { + for (FapConstraint& constraint : *constraints) { + constraint.impact = + EvaluateConstraintImpact(*variables, maximum_weight_cost, constraint); + } + } +} +} // namespace operations_research diff --git a/examples/cpp/fap_parser.h b/examples/cpp/fap_parser.h index adc8ef3b8e1..087d1473585 100644 --- a/examples/cpp/fap_parser.h +++ b/examples/cpp/fap_parser.h @@ -11,26 +11,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +// // Reading and parsing the data of Frequency Assignment Problem // Format: http://www.inra.fr/mia/T/schiex/Doc/CELAR.shtml#synt +// #ifndef OR_TOOLS_EXAMPLES_FAP_PARSER_H_ #define OR_TOOLS_EXAMPLES_FAP_PARSER_H_ -#include -#include #include #include #include "absl/container/btree_map.h" #include "absl/container/flat_hash_map.h" -#include "absl/strings/match.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_split.h" -#include "ortools/base/file.h" -#include "ortools/base/logging.h" -#include "ortools/base/macros.h" -#include "ortools/base/map_util.h" namespace operations_research { @@ -124,6 +117,11 @@ struct FapComponent { class VariableParser { public: explicit VariableParser(const std::string& data_directory); + + // This type is neither copyable nor movable. + VariableParser(const VariableParser&) = delete; + VariableParser& operator=(const VariableParser&) = delete; + ~VariableParser(); const absl::btree_map& variables() const { @@ -138,8 +136,6 @@ class VariableParser { // be consecutive, may be very sparse and don't have a specific upper-bound. // The key of the map, is the link's id. absl::btree_map variables_; - - DISALLOW_COPY_AND_ASSIGN(VariableParser); }; // Parser of the dom.txt file. @@ -148,6 +144,11 @@ class VariableParser { class DomainParser { public: explicit DomainParser(const std::string& data_directory); + + // This type is neither copyable nor movable. + DomainParser(const DomainParser&) = delete; + DomainParser& operator=(const DomainParser&) = delete; + ~DomainParser(); const absl::btree_map >& domains() const { @@ -162,8 +163,6 @@ class DomainParser { // domains may be random values, since they are used as names. The key of the // map is the subset's id. absl::btree_map > domains_; - - DISALLOW_COPY_AND_ASSIGN(DomainParser); }; // Parse ctr.txt file. @@ -172,6 +171,11 @@ class DomainParser { class ConstraintParser { public: explicit ConstraintParser(const std::string& data_directory); + + // This type is neither copyable nor movable. + ConstraintParser(const ConstraintParser&) = delete; + ConstraintParser& operator=(const ConstraintParser&) = delete; + ~ConstraintParser(); const std::vector& constraints() const { return constraints_; } @@ -181,8 +185,6 @@ class ConstraintParser { private: const std::string filename_; std::vector constraints_; - - DISALLOW_COPY_AND_ASSIGN(ConstraintParser); }; // Parse cst.txt file. @@ -212,14 +214,6 @@ class ParametersParser { std::vector variable_weights_; }; -namespace { -int strtoint32(const std::string& word) { - int result; - CHECK(absl::SimpleAtoi(word, &result)); - return result; -} -} // namespace - // Function that finds the disjoint sub-graphs of the graph of the instance. void FindComponents(const std::vector& constraints, const absl::btree_map& variables, @@ -236,357 +230,5 @@ void ParseInstance(const std::string& data_directory, bool find_components, std::vector* constraints, std::string* objective, std::vector* frequencies, absl::flat_hash_map* components); - -void ParseFileByLines(const std::string& filename, - std::vector* lines) { - CHECK(lines != nullptr); - std::string result; - CHECK_OK(file::GetContents(filename, &result, file::Defaults())); - *lines = absl::StrSplit(result, '\n', absl::SkipEmpty()); -} - -// VariableParser Implementation -VariableParser::VariableParser(const std::string& data_directory) - : filename_(data_directory + "/var.txt") {} - -VariableParser::~VariableParser() = default; - -void VariableParser::Parse() { - std::vector lines; - ParseFileByLines(filename_, &lines); - for (const std::string& line : lines) { - std::vector tokens = - absl::StrSplit(line, ' ', absl::SkipEmpty()); - if (tokens.empty()) { - continue; - } - CHECK_GE(tokens.size(), 2); - - FapVariable variable; - variable.domain_index = strtoint32(tokens[1]); - if (tokens.size() > 3) { - variable.initial_position = strtoint32(tokens[2]); - variable.mobility_index = strtoint32(tokens[3]); - } - gtl::InsertOrUpdate(&variables_, strtoint32(tokens[0]), variable); - } -} - -// DomainParser Implementation -DomainParser::DomainParser(const std::string& data_directory) - : filename_(data_directory + "/dom.txt") {} - -DomainParser::~DomainParser() = default; - -void DomainParser::Parse() { - std::vector lines; - ParseFileByLines(filename_, &lines); - for (const std::string& line : lines) { - std::vector tokens = - absl::StrSplit(line, ' ', absl::SkipEmpty()); - if (tokens.empty()) { - continue; - } - CHECK_GE(tokens.size(), 2); - - const int key = strtoint32(tokens[0]); - - std::vector domain; - domain.clear(); - for (int i = 2; i < tokens.size(); ++i) { - domain.push_back(strtoint32(tokens[i])); - } - - if (!domain.empty()) { - gtl::InsertOrUpdate(&domains_, key, domain); - } - } -} - -// ConstraintParser Implementation -ConstraintParser::ConstraintParser(const std::string& data_directory) - : filename_(data_directory + "/ctr.txt") {} - -ConstraintParser::~ConstraintParser() = default; - -void ConstraintParser::Parse() { - std::vector lines; - ParseFileByLines(filename_, &lines); - for (const std::string& line : lines) { - std::vector tokens = - absl::StrSplit(line, ' ', absl::SkipEmpty()); - if (tokens.empty()) { - continue; - } - CHECK_GE(tokens.size(), 5); - - FapConstraint constraint; - constraint.variable1 = strtoint32(tokens[0]); - constraint.variable2 = strtoint32(tokens[1]); - constraint.type = tokens[2]; - constraint.operation = tokens[3]; - constraint.value = strtoint32(tokens[4]); - - if (tokens.size() > 5) { - constraint.weight_index = strtoint32(tokens[5]); - } - constraints_.push_back(constraint); - } -} - -// ParametersParser Implementation -const int ParametersParser::kConstraintCoefficientNo; -const int ParametersParser::kVariableCoefficientNo; -const int ParametersParser::kCoefficientNo; - -ParametersParser::ParametersParser(const std::string& data_directory) - : filename_(data_directory + "/cst.txt"), - objective_(""), - constraint_weights_(kConstraintCoefficientNo, 0), - variable_weights_(kVariableCoefficientNo, 0) {} - -ParametersParser::~ParametersParser() = default; - -void ParametersParser::Parse() { - bool objective = true; - bool largest_token = false; - bool value_token = false; - bool number_token = false; - bool values_token = false; - bool coefficient = false; - std::vector coefficients; - std::vector lines; - - ParseFileByLines(filename_, &lines); - for (const std::string& line : lines) { - if (objective) { - largest_token = largest_token || absl::StrContains(line, "largest"); - value_token = value_token || absl::StrContains(line, "value"); - number_token = number_token || absl::StrContains(line, "number"); - values_token = values_token || absl::StrContains(line, "values"); - coefficient = coefficient || absl::StrContains(line, "coefficient"); - } - - if (coefficient) { - CHECK_EQ(kCoefficientNo, - kConstraintCoefficientNo + kVariableCoefficientNo); - objective = false; - if (absl::StrContains(line, "=")) { - std::vector tokens = - absl::StrSplit(line, ' ', absl::SkipEmpty()); - CHECK_GE(tokens.size(), 3); - coefficients.push_back(strtoint32(tokens[2])); - } - } - } - - if (coefficient) { - CHECK_EQ(kCoefficientNo, coefficients.size()); - for (int i = 0; i < kCoefficientNo; i++) { - if (i < kConstraintCoefficientNo) { - constraint_weights_[i] = coefficients[i]; - } else { - variable_weights_[i - kConstraintCoefficientNo] = coefficients[i]; - } - } - } - - if (largest_token && value_token) { - objective_ = "Minimize the largest assigned value."; - } else if (number_token && values_token) { - objective_ = "Minimize the number of assigned values."; - } else { - // Should not reach this point. - LOG(WARNING) << "Cannot read the objective of the instance."; - } -} - -// TODO(user): Make FindComponents linear instead of quadratic. -void FindComponents(const std::vector& constraints, - const absl::btree_map& variables, - const int maximum_variable_id, - absl::flat_hash_map* components) { - std::vector in_component(maximum_variable_id + 1, -1); - int constraint_index = 0; - for (const FapConstraint& constraint : constraints) { - const int variable_id1 = constraint.variable1; - const int variable_id2 = constraint.variable2; - const FapVariable& variable1 = gtl::FindOrDie(variables, variable_id1); - const FapVariable& variable2 = gtl::FindOrDie(variables, variable_id2); - CHECK_LT(variable_id1, in_component.size()); - CHECK_LT(variable_id2, in_component.size()); - if (in_component[variable_id1] < 0 && in_component[variable_id2] < 0) { - // None of the variables belong to an existing component. - // Create a new one. - FapComponent component; - const int component_index = constraint_index; - gtl::InsertOrUpdate(&(component.variables), variable_id1, variable1); - gtl::InsertOrUpdate(&(component.variables), variable_id2, variable2); - in_component[variable_id1] = component_index; - in_component[variable_id2] = component_index; - component.constraints.push_back(constraint); - gtl::InsertOrUpdate(components, component_index, component); - } else if (in_component[variable_id1] >= 0 && - in_component[variable_id2] < 0) { - // If variable1 belongs to an existing component, variable2 should - // also be included in the same component. - const int component_index = in_component[variable_id1]; - CHECK(components->contains(component_index)); - gtl::InsertOrUpdate(&((*components)[component_index].variables), - variable_id2, variable2); - in_component[variable_id2] = component_index; - (*components)[component_index].constraints.push_back(constraint); - } else if (in_component[variable_id1] < 0 && - in_component[variable_id2] >= 0) { - // If variable2 belongs to an existing component, variable1 should - // also be included in the same component. - const int component_index = in_component[variable_id2]; - CHECK(components->contains(component_index)); - gtl::InsertOrUpdate(&((*components)[component_index].variables), - variable_id1, variable1); - in_component[variable_id1] = component_index; - (*components)[component_index].constraints.push_back(constraint); - } else { - // The current constraint connects two different components. - const int component_index1 = in_component[variable_id1]; - const int component_index2 = in_component[variable_id2]; - const int min_component_index = - std::min(component_index1, component_index2); - const int max_component_index = - std::max(component_index1, component_index2); - CHECK(components->contains(min_component_index)); - CHECK(components->contains(max_component_index)); - if (min_component_index != max_component_index) { - // Update the component_index of maximum indexed component's variables. - for (const auto& variable : - (*components)[max_component_index].variables) { - int variable_id = variable.first; - in_component[variable_id] = min_component_index; - } - // Insert all the variables of the maximum indexed component to the - // variables of the minimum indexed component. - ((*components)[min_component_index]) - .variables.insert( - ((*components)[max_component_index]).variables.begin(), - ((*components)[max_component_index]).variables.end()); - // Insert all the constraints of the maximum indexed component to the - // constraints of the minimum indexed component. - ((*components)[min_component_index]) - .constraints.insert( - ((*components)[min_component_index]).constraints.end(), - ((*components)[max_component_index]).constraints.begin(), - ((*components)[max_component_index]).constraints.end()); - (*components)[min_component_index].constraints.push_back(constraint); - // Delete the maximum indexed component from the components set. - components->erase(max_component_index); - } else { - // Both variables belong to the same component, just add the constraint. - (*components)[min_component_index].constraints.push_back(constraint); - } - } - constraint_index++; - } -} - -int EvaluateConstraintImpact(const absl::btree_map& variables, - const int max_weight_cost, - const FapConstraint constraint) { - const FapVariable& variable1 = - gtl::FindOrDie(variables, constraint.variable1); - const FapVariable& variable2 = - gtl::FindOrDie(variables, constraint.variable2); - const int degree1 = variable1.degree; - const int degree2 = variable2.degree; - const int max_degree = std::max(degree1, degree2); - const int min_degree = std::min(degree1, degree2); - const int operator_impact = - constraint.operation == "=" ? max_degree : min_degree; - const int kHardnessBias = 10; - int hardness_impact = 0; - if (constraint.hard) { - hardness_impact = max_weight_cost > 0 ? kHardnessBias * max_weight_cost : 0; - } else { - hardness_impact = constraint.weight_cost; - } - return max_degree + min_degree + operator_impact + hardness_impact; -} - -void ParseInstance(const std::string& data_directory, bool find_components, - absl::btree_map* variables, - std::vector* constraints, - std::string* objective, std::vector* frequencies, - absl::flat_hash_map* components) { - CHECK(variables != nullptr); - CHECK(constraints != nullptr); - CHECK(objective != nullptr); - CHECK(frequencies != nullptr); - - // Parse the data files. - VariableParser var(data_directory); - var.Parse(); - *variables = var.variables(); - const int maximum_variable_id = variables->rbegin()->first; - - ConstraintParser ctr(data_directory); - ctr.Parse(); - *constraints = ctr.constraints(); - - DomainParser dom(data_directory); - dom.Parse(); - - ParametersParser cst(data_directory); - cst.Parse(); - const int maximum_weight_cost = *std::max_element( - (cst.constraint_weights()).begin(), (cst.constraint_weights()).end()); - - // Make the variables of the instance. - for (auto& it : *variables) { - it.second.domain = gtl::FindOrDie(dom.domains(), it.second.domain_index); - it.second.domain_size = it.second.domain.size(); - - if ((it.second.mobility_index == -1) || (it.second.mobility_index == 0)) { - it.second.mobility_cost = -1; - if (it.second.initial_position != -1) { - it.second.hard = true; - } - } else { - it.second.mobility_cost = - (cst.variable_weights())[it.second.mobility_index - 1]; - } - } - // Make the constraints of the instance. - for (FapConstraint& ct : *constraints) { - if ((ct.weight_index == -1) || (ct.weight_index == 0)) { - ct.weight_cost = -1; - ct.hard = true; - } else { - ct.weight_cost = (cst.constraint_weights())[ct.weight_index - 1]; - ct.hard = false; - } - ++((*variables)[ct.variable1]).degree; - ++((*variables)[ct.variable2]).degree; - } - // Make the available frequencies of the instance. - *frequencies = gtl::FindOrDie(dom.domains(), 0); - // Make the objective of the instance. - *objective = cst.objective(); - - if (find_components) { - CHECK(components != nullptr); - FindComponents(*constraints, *variables, maximum_variable_id, components); - // Evaluate each components's constraints impacts. - for (auto& component : *components) { - for (auto& constraint : component.second.constraints) { - constraint.impact = EvaluateConstraintImpact( - *variables, maximum_weight_cost, constraint); - } - } - } else { - for (FapConstraint& constraint : *constraints) { - constraint.impact = - EvaluateConstraintImpact(*variables, maximum_weight_cost, constraint); - } - } -} } // namespace operations_research #endif // OR_TOOLS_EXAMPLES_FAP_PARSER_H_ diff --git a/examples/cpp/fap_utilities.cc b/examples/cpp/fap_utilities.cc new file mode 100644 index 00000000000..551399338e5 --- /dev/null +++ b/examples/cpp/fap_utilities.cc @@ -0,0 +1,185 @@ +// Copyright 2010-2024 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 "examples/cpp/fap_utilities.h" + +#include +#include +#include + +#include "absl/container/btree_map.h" +#include "absl/container/btree_set.h" +#include "absl/types/span.h" +#include "ortools/base/logging.h" +#include "ortools/base/map_util.h" + +namespace operations_research { + +bool CheckConstraintSatisfaction( + absl::Span data_constraints, + absl::Span variables, + const absl::btree_map& index_from_key) { + bool status = true; + for (const FapConstraint& ct : data_constraints) { + const int index1 = gtl::FindOrDie(index_from_key, ct.variable1); + const int index2 = gtl::FindOrDie(index_from_key, ct.variable2); + CHECK_LT(index1, variables.size()); + CHECK_LT(index2, variables.size()); + const int var1 = variables[index1]; + const int var2 = variables[index2]; + const int absolute_difference = abs(var1 - var2); + + if ((ct.operation == ">") && (absolute_difference <= ct.value)) { + LOG(INFO) << " Violation of constraint between variable " << ct.variable1 + << " and variable " << ct.variable2 << "."; + LOG(INFO) << " Expected |" << var1 << " - " << var2 + << "| (= " << absolute_difference << ") > " << ct.value << "."; + status = false; + } else if ((ct.operation == "=") && (absolute_difference != ct.value)) { + LOG(INFO) << " Violation of constraint between variable " << ct.variable1 + << " and variable " << ct.variable2 << "."; + LOG(INFO) << " Expected |" << var1 << " - " << var2 + << "| (= " << absolute_difference << ") = " << ct.value << "."; + status = false; + } + } + return status; +} + +bool CheckVariablePosition( + const absl::btree_map& data_variables, + absl::Span variables, + const absl::btree_map& index_from_key) { + bool status = true; + for (const auto& it : data_variables) { + const int index = gtl::FindOrDie(index_from_key, it.first); + CHECK_LT(index, variables.size()); + const int var = variables[index]; + + if (it.second.hard && (it.second.initial_position != -1) && + (var != it.second.initial_position)) { + LOG(INFO) << " Change of position of hard variable " << it.first << "."; + LOG(INFO) << " Expected " << it.second.initial_position + << " instead of given " << var << "."; + status = false; + } + } + return status; +} + +int NumberOfAssignedValues(absl::Span variables) { + absl::btree_set assigned(variables.begin(), variables.end()); + return static_cast(assigned.size()); +} + +void PrintElapsedTime(const int64_t time1, const int64_t time2) { + LOG(INFO) << "End of solving process."; + LOG(INFO) << "The Solve method took " << (time2 - time1) / 1000.0 + << " seconds."; +} + +void PrintResultsHard(SolutionCollector* const collector, + const std::vector& variables, + IntVar* const objective_var, + const absl::btree_map& data_variables, + absl::Span data_constraints, + const absl::btree_map& index_from_key, + absl::Span key_from_index) { + LOG(INFO) << "Printing..."; + LOG(INFO) << "Number of Solutions: " << collector->solution_count(); + for (int solution_index = 0; solution_index < collector->solution_count(); + ++solution_index) { + Assignment* const solution = collector->solution(solution_index); + std::vector results(variables.size()); + LOG(INFO) << "------------------------------------------------------------"; + LOG(INFO) << "Solution " << solution_index + 1; + LOG(INFO) << "Cost: " << solution->Value(objective_var); + for (int i = 0; i < variables.size(); ++i) { + results[i] = solution->Value(variables[i]); + LOG(INFO) << " Variable " << key_from_index[i] << ": " << results[i]; + } + if (CheckConstraintSatisfaction(data_constraints, results, + index_from_key)) { + LOG(INFO) << "All hard constraints satisfied."; + } else { + LOG(INFO) << "Warning! Hard constraint violation detected."; + } + if (CheckVariablePosition(data_variables, results, index_from_key)) { + LOG(INFO) << "All hard variables stayed unharmed."; + } else { + LOG(INFO) << "Warning! Hard variable modification detected."; + } + + LOG(INFO) << "Values used: " << NumberOfAssignedValues(results); + LOG(INFO) << "Maximum value used: " + << *std::max_element(results.begin(), results.end()); + LOG(INFO) << " Failures: " << collector->failures(solution_index); + } + LOG(INFO) << " ============================================================"; +} + +void PrintResultsSoft(SolutionCollector* const collector, + const std::vector& variables, + IntVar* const total_cost, + const absl::btree_map& hard_variables, + absl::Span hard_constraints, + const absl::btree_map& soft_variables, + absl::Span soft_constraints, + const absl::btree_map& index_from_key, + absl::Span key_from_index) { + LOG(INFO) << "Printing..."; + LOG(INFO) << "Number of Solutions: " << collector->solution_count(); + for (int solution_index = 0; solution_index < collector->solution_count(); + ++solution_index) { + Assignment* const solution = collector->solution(solution_index); + std::vector results(variables.size()); + LOG(INFO) << "------------------------------------------------------------"; + LOG(INFO) << "Solution"; + for (int i = 0; i < variables.size(); ++i) { + results[i] = solution->Value(variables[i]); + LOG(INFO) << " Variable " << key_from_index[i] << ": " << results[i]; + } + if (CheckConstraintSatisfaction(hard_constraints, results, + index_from_key)) { + LOG(INFO) << "All hard constraints satisfied."; + } else { + LOG(INFO) << "Warning! Hard constraint violation detected."; + } + if (CheckVariablePosition(hard_variables, results, index_from_key)) { + LOG(INFO) << "All hard variables stayed unharmed."; + } else { + LOG(INFO) << "Warning! Hard constraint violation detected."; + } + + if (CheckConstraintSatisfaction(soft_constraints, results, + index_from_key) && + CheckVariablePosition(soft_variables, results, index_from_key)) { + LOG(INFO) << "Problem feasible: " + "Soft constraints and soft variables satisfied."; + LOG(INFO) << " Weighted Sum: " << solution->Value(total_cost); + } else { + LOG(INFO) << "Problem unfeasible. Optimized weighted sum of violations."; + LOG(INFO) << " Weighted Sum: " << solution->Value(total_cost); + } + + LOG(INFO) << "Values used: " << NumberOfAssignedValues(results); + LOG(INFO) << "Maximum value used: " + << *std::max_element(results.begin(), results.end()); + LOG(INFO) << " Failures: " << collector->failures(solution_index); + } + LOG(INFO) << " ============================================================"; +} + +} // namespace operations_research diff --git a/examples/cpp/fap_utilities.h b/examples/cpp/fap_utilities.h index 8e146a9d840..9b4754fffde 100644 --- a/examples/cpp/fap_utilities.h +++ b/examples/cpp/fap_utilities.h @@ -19,14 +19,11 @@ #define OR_TOOLS_EXAMPLES_FAP_UTILITIES_H_ #include -#include #include #include "absl/container/btree_map.h" -#include "absl/strings/str_format.h" +#include "absl/types/span.h" #include "examples/cpp/fap_parser.h" -#include "ortools/base/logging.h" -#include "ortools/base/map_util.h" #include "ortools/constraint_solver/constraint_solver.h" namespace operations_research { @@ -34,19 +31,19 @@ namespace operations_research { // Checks if the solution given from the Solver satisfies all // the hard binary constraints specified in the ctr.txt. bool CheckConstraintSatisfaction( - const std::vector& data_constraints, - const std::vector& variables, + absl::Span data_constraints, + absl::Span variables, const absl::btree_map& index_from_key); // Checks if the solution given from the Solver has not modified the values of // the variables that were initially assigned and denoted as hard in var.txt. bool CheckVariablePosition( const absl::btree_map& data_variables, - const std::vector& variables, + absl::Span variables, const absl::btree_map& index_from_key); // Counts the number of different values in the variable vector. -int NumberOfAssignedValues(const std::vector& variables); +int NumberOfAssignedValues(absl::Span variables); // Prints the duration of the solving process. void PrintElapsedTime(int64_t time1, int64_t time2); @@ -56,173 +53,19 @@ void PrintResultsHard(SolutionCollector* collector, const std::vector& variables, IntVar* objective_var, const absl::btree_map& data_variables, - const std::vector& data_constraints, + absl::Span data_constraints, const absl::btree_map& index_from_key, - const std::vector& key_from_index); + absl::Span key_from_index); // Prints the solution found by the Soft Solver for unfeasible instances. void PrintResultsSoft(SolutionCollector* collector, const std::vector& variables, IntVar* total_cost, const absl::btree_map& hard_variables, - const std::vector& hard_constraints, + absl::Span hard_constraints, const absl::btree_map& soft_variables, - const std::vector& soft_constraints, + absl::Span soft_constraints, const absl::btree_map& index_from_key, - const std::vector& key_from_index); - -bool CheckConstraintSatisfaction( - const std::vector& data_constraints, - const std::vector& variables, - const absl::btree_map& index_from_key) { - bool status = true; - for (const FapConstraint& ct : data_constraints) { - const int index1 = gtl::FindOrDie(index_from_key, ct.variable1); - const int index2 = gtl::FindOrDie(index_from_key, ct.variable2); - CHECK_LT(index1, variables.size()); - CHECK_LT(index2, variables.size()); - const int var1 = variables[index1]; - const int var2 = variables[index2]; - const int absolute_difference = abs(var1 - var2); - - if ((ct.operation == ">") && (absolute_difference <= ct.value)) { - LOG(INFO) << " Violation of contraint between variable " << ct.variable1 - << " and variable " << ct.variable2 << "."; - LOG(INFO) << " Expected |" << var1 << " - " << var2 - << "| (= " << absolute_difference << ") > " << ct.value << "."; - status = false; - } else if ((ct.operation == "=") && (absolute_difference != ct.value)) { - LOG(INFO) << " Violation of contraint between variable " << ct.variable1 - << " and variable " << ct.variable2 << "."; - LOG(INFO) << " Expected |" << var1 << " - " << var2 - << "| (= " << absolute_difference << ") = " << ct.value << "."; - status = false; - } - } - return status; -} - -bool CheckVariablePosition(const absl::btree_map& data_variables, - const std::vector& variables, - const absl::btree_map& index_from_key) { - bool status = true; - for (const auto& it : data_variables) { - const int index = gtl::FindOrDie(index_from_key, it.first); - CHECK_LT(index, variables.size()); - const int var = variables[index]; - - if (it.second.hard && (it.second.initial_position != -1) && - (var != it.second.initial_position)) { - LOG(INFO) << " Change of position of hard variable " << it.first << "."; - LOG(INFO) << " Expected " << it.second.initial_position - << " instead of given " << var << "."; - status = false; - } - } - return status; -} - -int NumberOfAssignedValues(const std::vector& variables) { - std::set assigned(variables.begin(), variables.end()); - return static_cast(assigned.size()); -} - -void PrintElapsedTime(const int64_t time1, const int64_t time2) { - LOG(INFO) << "End of solving process."; - LOG(INFO) << "The Solve method took " << (time2 - time1) / 1000.0 - << " seconds."; -} - -void PrintResultsHard(SolutionCollector* const collector, - const std::vector& variables, - IntVar* const objective_var, - const absl::btree_map& data_variables, - const std::vector& data_constraints, - const absl::btree_map& index_from_key, - const std::vector& key_from_index) { - LOG(INFO) << "Printing..."; - LOG(INFO) << "Number of Solutions: " << collector->solution_count(); - for (int solution_index = 0; solution_index < collector->solution_count(); - ++solution_index) { - Assignment* const solution = collector->solution(solution_index); - std::vector results(variables.size()); - LOG(INFO) << "------------------------------------------------------------"; - LOG(INFO) << "Solution " << solution_index + 1; - LOG(INFO) << "Cost: " << solution->Value(objective_var); - for (int i = 0; i < variables.size(); ++i) { - results[i] = solution->Value(variables[i]); - LOG(INFO) << " Variable " << key_from_index[i] << ": " << results[i]; - } - if (CheckConstraintSatisfaction(data_constraints, results, - index_from_key)) { - LOG(INFO) << "All hard constraints satisfied."; - } else { - LOG(INFO) << "Warning! Hard constraint violation detected."; - } - if (CheckVariablePosition(data_variables, results, index_from_key)) { - LOG(INFO) << "All hard variables stayed unharmed."; - } else { - LOG(INFO) << "Warning! Hard variable modification detected."; - } - - LOG(INFO) << "Values used: " << NumberOfAssignedValues(results); - LOG(INFO) << "Maximum value used: " - << *std::max_element(results.begin(), results.end()); - LOG(INFO) << " Failures: " << collector->failures(solution_index); - } - LOG(INFO) << " ============================================================"; -} - -void PrintResultsSoft(SolutionCollector* const collector, - const std::vector& variables, - IntVar* const total_cost, - const absl::btree_map& hard_variables, - const std::vector& hard_constraints, - const absl::btree_map& soft_variables, - const std::vector& soft_constraints, - const absl::btree_map& index_from_key, - const std::vector& key_from_index) { - LOG(INFO) << "Printing..."; - LOG(INFO) << "Number of Solutions: " << collector->solution_count(); - for (int solution_index = 0; solution_index < collector->solution_count(); - ++solution_index) { - Assignment* const solution = collector->solution(solution_index); - std::vector results(variables.size()); - LOG(INFO) << "------------------------------------------------------------"; - LOG(INFO) << "Solution"; - for (int i = 0; i < variables.size(); ++i) { - results[i] = solution->Value(variables[i]); - LOG(INFO) << " Variable " << key_from_index[i] << ": " << results[i]; - } - if (CheckConstraintSatisfaction(hard_constraints, results, - index_from_key)) { - LOG(INFO) << "All hard constraints satisfied."; - } else { - LOG(INFO) << "Warning! Hard constraint violation detected."; - } - if (CheckVariablePosition(hard_variables, results, index_from_key)) { - LOG(INFO) << "All hard variables stayed unharmed."; - } else { - LOG(INFO) << "Warning! Hard constraint violation detected."; - } - - if (CheckConstraintSatisfaction(soft_constraints, results, - index_from_key) && - CheckVariablePosition(soft_variables, results, index_from_key)) { - LOG(INFO) << "Problem feasible: " - "Soft constraints and soft variables satisfied."; - LOG(INFO) << " Weighted Sum: " << solution->Value(total_cost); - } else { - LOG(INFO) << "Problem unfeasible. Optimized weighted sum of violations."; - LOG(INFO) << " Weighted Sum: " << solution->Value(total_cost); - } - - LOG(INFO) << "Values used: " << NumberOfAssignedValues(results); - LOG(INFO) << "Maximum value used: " - << *std::max_element(results.begin(), results.end()); - LOG(INFO) << " Failures: " << collector->failures(solution_index); - } - LOG(INFO) << " ============================================================"; -} + absl::Span key_from_index); } // namespace operations_research #endif // OR_TOOLS_EXAMPLES_FAP_UTILITIES_H_ diff --git a/examples/cpp/max_flow.cc b/examples/cpp/max_flow.cc index 70a8db8e75a..0524f70d57f 100644 --- a/examples/cpp/max_flow.cc +++ b/examples/cpp/max_flow.cc @@ -26,7 +26,7 @@ using Graph = ::util::ReverseArcListGraph<>; using NodeIndex = Graph::NodeIndex; using ArcIndex = Graph::ArcIndex; using MaxFlowT = GenericMaxFlow; -using FlowQuantity = MaxFlow::FlowQuantityT; +using FlowQuantity = MaxFlowT::FlowQuantityT; void SolveMaxFlow() { const int num_nodes = 5; diff --git a/examples/cpp/mps_driver.cc b/examples/cpp/mps_driver.cc index d23ec802802..0993a9482b1 100644 --- a/examples/cpp/mps_driver.cc +++ b/examples/cpp/mps_driver.cc @@ -112,7 +112,11 @@ int main(int argc, char* argv[]) { continue; } } else { - ReadFileToProto(file_name, &model_proto); + const absl::Status status = ReadFileToProto(file_name, &model_proto); + if (!status.ok()) { + LOG(INFO) << status; + continue; + } MPModelProtoToLinearProgram(model_proto, &linear_program); } if (absl::GetFlag(FLAGS_mps_dump_problem)) { diff --git a/examples/cpp/network_routing_sat.cc b/examples/cpp/network_routing_sat.cc index 5bf8f6d74f3..ba47b3d740d 100644 --- a/examples/cpp/network_routing_sat.cc +++ b/examples/cpp/network_routing_sat.cc @@ -499,13 +499,13 @@ class NetworkRoutingSolver { const std::vector distances(2 * count_arcs(), 1); for (const Demand& demand : demands_array_) { - PathContainer paths; - PathContainer::BuildInMemoryCompactPathContainer(&paths); + auto paths = + GenericPathContainer::BuildInMemoryCompactPathContainer(); ComputeOneToManyShortestPaths(graph_, distances, demand.source, {demand.destination}, &paths); - std::vector path; + std::vector path; paths.GetPath(demand.source, demand.destination, &path); CHECK_GE(path.size(), 1); all_min_path_lengths_.push_back(path.size() - 1); @@ -656,13 +656,14 @@ class NetworkRoutingSolver { } private: + using Graph = ::util::ListGraph; int num_nodes() const { return graph_.num_nodes(); } int count_arcs() const { return arcs_data_.size() / 2; } std::vector> arcs_data_; std::vector arc_capacity_; std::vector demands_array_; - util::ListGraph graph_; + Graph graph_; std::vector all_min_path_lengths_; std::vector> capacity_; std::vector> all_paths_; diff --git a/examples/cpp/parse_dimacs_assignment.cc b/examples/cpp/parse_dimacs_assignment.cc new file mode 100644 index 00000000000..e67b57a63ab --- /dev/null +++ b/examples/cpp/parse_dimacs_assignment.cc @@ -0,0 +1,19 @@ +// Copyright 2010-2024 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 "examples/cpp/parse_dimacs_assignment.h" + +#include "absl/flags/flag.h" + +ABSL_FLAG(bool, assignment_maximize_cost, false, + "Negate costs so a max-cost assignment is found."); diff --git a/examples/cpp/parse_dimacs_assignment.h b/examples/cpp/parse_dimacs_assignment.h index cbd684a44db..22b1e2700ba 100644 --- a/examples/cpp/parse_dimacs_assignment.h +++ b/examples/cpp/parse_dimacs_assignment.h @@ -33,17 +33,16 @@ #include "ortools/graph/linear_assignment.h" #include "ortools/util/filelineiter.h" -ABSL_FLAG(bool, assignment_maximize_cost, false, - "Negate costs so a max-cost assignment is found."); +ABSL_DECLARE_FLAG(bool, assignment_maximize_cost); namespace operations_research { template class DimacsAssignmentParser { public: - using NodeIndex = GraphType::NodeIndex; - using ArcIndex = GraphType::ArcIndex; - using CostValue = LinearSumAssignment::CostValueT; + using NodeIndex = typename GraphType::NodeIndex; + using ArcIndex = typename GraphType::ArcIndex; + using CostValue = typename LinearSumAssignment::CostValueT; explicit DimacsAssignmentParser(absl::string_view filename) : filename_(filename), graph_(nullptr), assignment_(nullptr) {} diff --git a/ortools/graph/BUILD.bazel b/ortools/graph/BUILD.bazel index a07e0d354d3..a66cf2ed927 100644 --- a/ortools/graph/BUILD.bazel +++ b/ortools/graph/BUILD.bazel @@ -378,20 +378,17 @@ cc_test( cc_library( name = "shortest_paths", - srcs = ["shortest_paths.cc"], hdrs = ["shortest_paths.h"], deps = [ - ":ebert_graph", - ":graph", "//ortools/base", "//ortools/base:adjustable_priority_queue", "//ortools/base:map_util", "//ortools/base:stl_util", "//ortools/base:threadpool", "//ortools/base:timer", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/functional:bind_front", - "@com_google_absl//absl/log", "@com_google_absl//absl/log:check", "@com_google_absl//absl/types:span", ], @@ -404,10 +401,10 @@ cc_test( tags = ["noasan"], # Times out occasionally in ASAN mode. deps = [ ":ebert_graph", + ":graph", ":shortest_paths", ":strongly_connected_components", "//ortools/base:gmock_main", - "//ortools/util:zvector", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/log:check", "@com_google_absl//absl/random", @@ -537,11 +534,11 @@ cc_library( ":generic_max_flow", ":graph", ":graphs", - ":max_flow", "//ortools/base:mathutil", "//ortools/util:saturated_arithmetic", "//ortools/util:stats", "//ortools/util:zvector", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/log", "@com_google_absl//absl/log:check", diff --git a/ortools/graph/assignment.cc b/ortools/graph/assignment.cc index 7ec3e97f523..fff068f4b3f 100644 --- a/ortools/graph/assignment.cc +++ b/ortools/graph/assignment.cc @@ -14,7 +14,6 @@ #include "ortools/graph/assignment.h" #include -#include #include #include "ortools/graph/graph.h" @@ -22,15 +21,10 @@ namespace operations_research { -using ArcIndex = int32_t; -using NodeIndex = int32_t; -using CostValue = int64_t; - SimpleLinearSumAssignment::SimpleLinearSumAssignment() : num_nodes_(0) {} -ArcIndex SimpleLinearSumAssignment::AddArcWithCost(NodeIndex left_node, - NodeIndex right_node, - CostValue cost) { +SimpleLinearSumAssignment::ArcIndex SimpleLinearSumAssignment::AddArcWithCost( + NodeIndex left_node, NodeIndex right_node, CostValue cost) { const ArcIndex num_arcs = arc_cost_.size(); num_nodes_ = std::max(num_nodes_, left_node + 1); num_nodes_ = std::max(num_nodes_, right_node + 1); @@ -40,19 +34,27 @@ ArcIndex SimpleLinearSumAssignment::AddArcWithCost(NodeIndex left_node, return num_arcs; } -NodeIndex SimpleLinearSumAssignment::NumNodes() const { return num_nodes_; } +SimpleLinearSumAssignment::NodeIndex SimpleLinearSumAssignment::NumNodes() + const { + return num_nodes_; +} -ArcIndex SimpleLinearSumAssignment::NumArcs() const { return arc_cost_.size(); } +SimpleLinearSumAssignment::ArcIndex SimpleLinearSumAssignment::NumArcs() const { + return arc_cost_.size(); +} -NodeIndex SimpleLinearSumAssignment::LeftNode(ArcIndex arc) const { +SimpleLinearSumAssignment::NodeIndex SimpleLinearSumAssignment::LeftNode( + ArcIndex arc) const { return arc_tail_[arc]; } -NodeIndex SimpleLinearSumAssignment::RightNode(ArcIndex arc) const { +SimpleLinearSumAssignment::NodeIndex SimpleLinearSumAssignment::RightNode( + ArcIndex arc) const { return arc_head_[arc]; } -CostValue SimpleLinearSumAssignment::Cost(ArcIndex arc) const { +SimpleLinearSumAssignment::CostValue SimpleLinearSumAssignment::Cost( + ArcIndex arc) const { return arc_cost_[arc]; } diff --git a/ortools/graph/ebert_graph.h b/ortools/graph/ebert_graph.h index 2589ec0fcbf..106d250f232 100644 --- a/ortools/graph/ebert_graph.h +++ b/ortools/graph/ebert_graph.h @@ -32,10 +32,7 @@ // those commonalities are mostly factored out into base classes as // described below. Despite the commonalities, however, each of the // three representations presents a somewhat different interface -// because of their different underlying semantics. A quintessential -// example is that the AddArc() method, very natural for the -// EbertGraph representation, cannot exist for an inherently static -// representation like ForwardStaticGraph. +// because of their different underlying semantics. // // Many clients are expected to use the interfaces to the graph // objects directly, but some clients are parameterized by graph type @@ -48,8 +45,7 @@ // TailArrayManager<> template, which provides a uniform interface for // applications that need to map from arc indices to arc tail nodes, // accounting for the fact that such a mapping has to be requested -// explicitly from the ForwardStaticGraph and ForwardStarGraph -// representations. +// explicitly from the ForwardStarGraph representation. // // There are two base class templates, StarGraphBase, and // EbertGraphBase; their purpose is to hold methods and data @@ -60,11 +56,11 @@ // not normally be instantiated by clients: // // (StarGraphBase) | -// / \ | -// / \ | -// / \ | -// / \ | -// (EbertGraphBase) ForwardStaticGraph | +// / | +// / | +// / | +// / | +// (EbertGraphBase) | // / \ | // / \ | // EbertGraph ForwardEbertGraph | @@ -151,21 +147,6 @@ // + n * sizeof(ArcIndexType) // plus a small constant when the array of arc tails is absent. Allocating // the arc tail array adds another m * sizeof(NodeIndexType). -// -// The ForwardStaticGraph representation is restricted yet farther -// than ForwardEbertGraph, with the benefit that it provides higher -// performance to those applications that can use it. -// * As with ForwardEbertGraph, the presence of the array of arc -// tails is optional. -// * The outgoing adjacency list for each node is stored in a -// contiguous segment of the head_[] array, obviating the -// next_adjacent_arc_ structure entirely and ensuring good locality -// of reference for applications that iterate over outgoing -// adjacency lists. -// * The memory consumption is: m * sizeof(NodeIndexType) -// + n * sizeof(ArcIndexType) -// plus a small constant when the array of arc tails is absent. Allocating -// the arc tail array adds another m * sizeof(NodeIndexType). #include #include @@ -191,8 +172,6 @@ template class EbertGraph; template class ForwardEbertGraph; -template -class ForwardStaticGraph; // Standard instantiation of ForwardEbertGraph (named 'ForwardStarGraph') of // EbertGraph (named 'StarGraph'); and relevant type shortcuts. Unless their use @@ -206,10 +185,6 @@ typedef int64_t FlowQuantity; typedef int64_t CostValue; typedef EbertGraph StarGraph; typedef ForwardEbertGraph ForwardStarGraph; -typedef ZVector NodeIndexArray; -typedef ZVector ArcIndexArray; -typedef ZVector QuantityArray; -typedef ZVector CostArray; // Adapt our old iteration style to support range-based for loops. Add typedefs // required by std::iterator_traits. @@ -536,387 +511,6 @@ class StarGraphBase { } }; -template -class PermutationIndexComparisonByArcHead { - public: - explicit PermutationIndexComparisonByArcHead( - const ZVector& head) - : head_(head) {} - - bool operator()(ArcIndexType a, ArcIndexType b) const { - return head_[a] < head_[b]; - } - - private: - const ZVector& head_; -}; - -template -class ABSL_DEPRECATED("Use `::util::StaticGraph<>` instead.") ForwardStaticGraph - : public StarGraphBase > { - typedef StarGraphBase > - Base; - friend class StarGraphBase >; - - using Base::ArcDebugString; - using Base::NodeDebugString; - - using Base::first_incident_arc_; - using Base::head_; - using Base::max_num_arcs_; - using Base::max_num_nodes_; - using Base::num_arcs_; - using Base::num_nodes_; - - public: -#if !defined(SWIG) - using Base::end_arc_index; - using Base::Head; - using Base::IsNodeValid; - - using Base::kFirstArc; - using Base::kFirstNode; - using Base::kNilArc; -#endif // SWIG - - typedef NodeIndexType NodeIndex; - typedef ArcIndexType ArcIndex; - -// TODO(user): Configure SWIG to handle the -// CycleHandlerForAnnotatedArcs class. -#if !defined(SWIG) - class CycleHandlerForAnnotatedArcs - : public ArrayIndexCycleHandler { - typedef ArrayIndexCycleHandler Base; - - public: - CycleHandlerForAnnotatedArcs( - PermutationCycleHandler* annotation_handler, - NodeIndexType* data) - : ArrayIndexCycleHandler(&data[kFirstArc]), - annotation_handler_(annotation_handler) {} - - // This type is neither copyable nor movable. - CycleHandlerForAnnotatedArcs(const CycleHandlerForAnnotatedArcs&) = delete; - CycleHandlerForAnnotatedArcs& operator=( - const CycleHandlerForAnnotatedArcs&) = delete; - - void SetTempFromIndex(ArcIndexType source) override { - Base::SetTempFromIndex(source); - annotation_handler_->SetTempFromIndex(source); - } - - void SetIndexFromIndex(ArcIndexType source, - ArcIndexType destination) const override { - Base::SetIndexFromIndex(source, destination); - annotation_handler_->SetIndexFromIndex(source, destination); - } - - void SetIndexFromTemp(ArcIndexType destination) const override { - Base::SetIndexFromTemp(destination); - annotation_handler_->SetIndexFromTemp(destination); - } - - private: - PermutationCycleHandler* annotation_handler_; - }; -#endif // SWIG - - // Constructor for use by GraphBuilderFromArcs instances and direct - // clients that want to materialize a graph in one step. - // Materializing all at once is the only choice available with a - // static graph. - // - // Args: - // sort_arcs_by_head: determines whether arcs incident to each tail - // node are sorted by head node. - // client_cycle_handler: if non-NULL, mediates the permutation of - // arbitrary annotation data belonging to the client according - // to the permutation applied to the arcs in forming the - // graph. Two permutations may be composed to form the final one - // that affects the arcs. First, the arcs are always permuted to - // group them by tail node because ForwardStaticGraph requires - // this. Second, if each node's outgoing arcs are sorted by head - // node (according to sort_arcs_by_head), that sorting implies - // an additional permutation on the arcs. - ForwardStaticGraph( - const NodeIndexType num_nodes, const ArcIndexType num_arcs, - const bool sort_arcs_by_head, - std::vector >* client_input_arcs, - // TODO(user): For some reason, SWIG breaks if the - // operations_research namespace is not explicit in the - // following argument declaration. - operations_research::PermutationCycleHandler* const - client_cycle_handler) { - max_num_arcs_ = num_arcs; - num_arcs_ = num_arcs; - max_num_nodes_ = num_nodes; - // A more convenient name for a parameter required by style to be - // a pointer, because we modify its referent. - std::vector >& input_arcs = - *client_input_arcs; - - // We coopt the first_incident_arc_ array as a node-indexed vector - // used for two purposes related to degree before setting up its - // final values. First, it counts the out-degree of each - // node. Second, it is reused to count the number of arcs outgoing - // from each node that have already been put in place from the - // given input_arcs. We reserve an extra entry as a sentinel at - // the end. - first_incident_arc_.Reserve(kFirstNode, kFirstNode + num_nodes); - first_incident_arc_.SetAll(0); - for (ArcIndexType arc = kFirstArc; arc < kFirstArc + num_arcs; ++arc) { - first_incident_arc_[kFirstNode + input_arcs[arc].first] += 1; - // Take this opportunity to see how many nodes are really - // mentioned in the arc list. - num_nodes_ = std::max( - num_nodes_, static_cast(input_arcs[arc].first + 1)); - num_nodes_ = std::max( - num_nodes_, static_cast(input_arcs[arc].second + 1)); - } - ArcIndexType next_arc = kFirstArc; - for (NodeIndexType node = 0; node < num_nodes; ++node) { - ArcIndexType degree = first_incident_arc_[kFirstNode + node]; - first_incident_arc_[kFirstNode + node] = next_arc; - next_arc += degree; - } - DCHECK_EQ(num_arcs, next_arc); - head_.Reserve(kFirstArc, kFirstArc + num_arcs - 1); - std::unique_ptr arc_permutation; - if (client_cycle_handler != nullptr) { - arc_permutation.reset(new ArcIndexType[end_arc_index()]); - for (ArcIndexType input_arc = 0; input_arc < num_arcs; ++input_arc) { - NodeIndexType tail = input_arcs[input_arc].first; - NodeIndexType head = input_arcs[input_arc].second; - ArcIndexType arc = first_incident_arc_[kFirstNode + tail]; - // The head_ entry will get permuted into the right place - // later. - head_[kFirstArc + input_arc] = kFirstNode + head; - arc_permutation[kFirstArc + arc] = input_arc; - first_incident_arc_[kFirstNode + tail] += 1; - } - } else { - if (sizeof(input_arcs[0].first) >= sizeof(first_incident_arc_[0])) { - // We reuse the input_arcs[].first entries to hold our - // mapping to the head_ array. This allows us to spread out - // cache badness. - for (ArcIndexType input_arc = 0; input_arc < num_arcs; ++input_arc) { - NodeIndexType tail = input_arcs[input_arc].first; - ArcIndexType arc = first_incident_arc_[kFirstNode + tail]; - first_incident_arc_[kFirstNode + tail] = arc + 1; - input_arcs[input_arc].first = static_cast(arc); - } - for (ArcIndexType input_arc = 0; input_arc < num_arcs; ++input_arc) { - ArcIndexType arc = - static_cast(input_arcs[input_arc].first); - NodeIndexType head = input_arcs[input_arc].second; - head_[kFirstArc + arc] = kFirstNode + head; - } - } else { - // We cannot reuse the input_arcs[].first entries so we map to - // the head_ array in a single loop. - for (ArcIndexType input_arc = 0; input_arc < num_arcs; ++input_arc) { - NodeIndexType tail = input_arcs[input_arc].first; - NodeIndexType head = input_arcs[input_arc].second; - ArcIndexType arc = first_incident_arc_[kFirstNode + tail]; - first_incident_arc_[kFirstNode + tail] = arc + 1; - head_[kFirstArc + arc] = kFirstNode + head; - } - } - } - // Shift the entries in first_incident_arc_ to compensate for the - // counting each one has done through its incident arcs. Note that - // there is a special sentry element at the end of - // first_incident_arc_. - for (NodeIndexType node = kFirstNode + num_nodes; node > /* kFirstNode */ 0; - --node) { - first_incident_arc_[node] = first_incident_arc_[node - 1]; - } - first_incident_arc_[kFirstNode] = kFirstArc; - if (sort_arcs_by_head) { - ArcIndexType begin = first_incident_arc_[kFirstNode]; - if (client_cycle_handler != nullptr) { - for (NodeIndexType node = 0; node < num_nodes; ++node) { - ArcIndexType end = first_incident_arc_[node + 1]; - std::sort( - &arc_permutation[begin], &arc_permutation[end], - PermutationIndexComparisonByArcHead( - head_)); - begin = end; - } - } else { - for (NodeIndexType node = 0; node < num_nodes; ++node) { - ArcIndexType end = first_incident_arc_[node + 1]; - // The second argument in the following has a strange index - // expression because ZVector claims that no index is valid - // unless it refers to an element in the vector. In particular - // an index one past the end is invalid. - ArcIndexType begin_index = (begin < num_arcs ? begin : begin - 1); - ArcIndexType begin_offset = (begin < num_arcs ? 0 : 1); - ArcIndexType end_index = (end > 0 ? end - 1 : end); - ArcIndexType end_offset = (end > 0 ? 1 : 0); - std::sort(&head_[begin_index] + begin_offset, - &head_[end_index] + end_offset); - begin = end; - } - } - } - if (client_cycle_handler != nullptr && num_arcs > 0) { - // Apply the computed permutation if we haven't already. - CycleHandlerForAnnotatedArcs handler_for_constructor( - client_cycle_handler, &head_[kFirstArc] - kFirstArc); - // We use a permutation cycle handler to place the head array - // indices and permute the client's arc annotation data along - // with them. - PermutationApplier permutation(&handler_for_constructor); - permutation.Apply(&arc_permutation[0], kFirstArc, end_arc_index()); - } - } - - // Returns the tail or start-node of arc. - NodeIndexType Tail(const ArcIndexType arc) const { - DCHECK(CheckArcValidity(arc)); - DCHECK(CheckTailIndexValidity(arc)); - return (*tail_)[arc]; - } - - // Returns true if arc is incoming to node. - bool IsIncoming(ArcIndexType arc, NodeIndexType node) const { - return Head(arc) == node; - } - - // Utility function to check that an arc index is within the bounds. - // It is exported so that users of the ForwardStaticGraph class can use it. - // To be used in a DCHECK. - bool CheckArcBounds(const ArcIndexType arc) const { - return ((arc == kNilArc) || (arc >= kFirstArc && arc < max_num_arcs_)); - } - - // Utility function to check that an arc index is within the bounds AND - // different from kNilArc. - // It is exported so that users of the ForwardStaticGraph class can use it. - // To be used in a DCHECK. - bool CheckArcValidity(const ArcIndexType arc) const { - return ((arc != kNilArc) && (arc >= kFirstArc && arc < max_num_arcs_)); - } - - // Returns true if arc is a valid index into the (*tail_) array. - bool CheckTailIndexValidity(const ArcIndexType arc) const { - return ((tail_ != nullptr) && (arc >= kFirstArc) && - (arc <= tail_->max_index())); - } - - ArcIndexType NextOutgoingArc(const NodeIndexType node, - ArcIndexType arc) const { - DCHECK(IsNodeValid(node)); - DCHECK(CheckArcValidity(arc)); - ++arc; - if (arc < first_incident_arc_[node + 1]) { - return arc; - } else { - return kNilArc; - } - } - - // Returns a debug string containing all the information contained in the - // data structure in raw form. - std::string DebugString() const { - std::string result = "Arcs:(node) :\n"; - for (ArcIndexType arc = kFirstArc; arc < num_arcs_; ++arc) { - result += " " + ArcDebugString(arc) + ":(" + NodeDebugString(head_[arc]) + - ")\n"; - } - result += "Node:First arc :\n"; - for (NodeIndexType node = kFirstNode; node <= num_nodes_; ++node) { - result += " " + NodeDebugString(node) + ":" + - ArcDebugString(first_incident_arc_[node]) + "\n"; - } - return result; - } - - bool BuildTailArray() { - // If (*tail_) is already allocated, we have the invariant that - // its contents are canonical, so we do not need to do anything - // here in that case except return true. - if (tail_ == nullptr) { - if (!RepresentationClean()) { - // We have been asked to build the (*tail_) array, but we have - // no valid information from which to build it. The graph is - // in an unrecoverable, inconsistent state. - return false; - } - // Reallocate (*tail_) and rebuild its contents from the - // adjacency lists. - tail_.reset(new ZVector); - tail_->Reserve(kFirstArc, max_num_arcs_ - 1); - typename Base::NodeIterator node_it(*this); - for (; node_it.Ok(); node_it.Next()) { - NodeIndexType node = node_it.Index(); - typename Base::OutgoingArcIterator arc_it(*this, node); - for (; arc_it.Ok(); arc_it.Next()) { - (*tail_)[arc_it.Index()] = node; - } - } - } - DCHECK(TailArrayComplete()); - return true; - } - - void ReleaseTailArray() { tail_.reset(nullptr); } - - // To be used in a DCHECK(). - bool TailArrayComplete() const { - CHECK(tail_); - for (ArcIndexType arc = kFirstArc; arc < num_arcs_; ++arc) { - CHECK(CheckTailIndexValidity(arc)); - CHECK(IsNodeValid((*tail_)[arc])); - } - return true; - } - - private: - bool IsDirect() const { return true; } - bool RepresentationClean() const { return true; } - bool IsOutgoing(const NodeIndexType node, - const ArcIndexType unused_arc) const { - return true; - } - - // Returns the first arc in node's incidence list. - ArcIndexType FirstOutgoingOrOppositeIncomingArc(NodeIndexType node) const { - DCHECK(RepresentationClean()); - DCHECK(IsNodeValid(node)); - ArcIndexType result = first_incident_arc_[node]; - return ((result != first_incident_arc_[node + 1]) ? result : kNilArc); - } - - // Utility method that finds the next outgoing arc. - ArcIndexType FindNextOutgoingArc(ArcIndexType arc) const { - DCHECK(CheckArcBounds(arc)); - return arc; - } - - // Array of node indices, not always present. (*tail_)[i] contains - // the tail node of arc i. This array is not needed for normal graph - // traversal operations, but is used in optimizing the graph's - // layout so arcs are grouped by tail node, and can be used in one - // approach to serializing the graph. - // - // Invariants: At any time when we are not executing a method of - // this class, either tail_ == NULL or the tail_ array's contents - // are kept canonical. If tail_ != NULL, any method that modifies - // adjacency lists must also ensure (*tail_) is modified - // correspondingly. The converse does not hold: Modifications to - // (*tail_) are allowed without updating the adjacency lists. If - // such modifications take place, representation_clean_ must be set - // to false, of course, to indicate that the adjacency lists are no - // longer current. - std::unique_ptr > tail_; -}; - // The index of the 'nil' node in the graph. template const NodeIndexType @@ -1913,12 +1507,6 @@ struct graph_traits > { static constexpr bool is_dynamic = true; }; -template -struct graph_traits > { - static constexpr bool has_reverse_arcs = false; - static constexpr bool is_dynamic = false; -}; - namespace or_internal { // The TailArrayBuilder class template is not expected to be used by diff --git a/ortools/graph/ebert_graph_test.cc b/ortools/graph/ebert_graph_test.cc index a50f1ba5c39..6a2ba5c6d9f 100644 --- a/ortools/graph/ebert_graph_test.cc +++ b/ortools/graph/ebert_graph_test.cc @@ -633,9 +633,8 @@ TYPED_TEST(DebugStringEbertGraphTest, Test2) { template class DebugStringTestWithGraphBuildManager : public ::testing::Test {}; -typedef ::testing::Types< - EbertGraph, ForwardEbertGraph, - ForwardStaticGraph, ForwardStaticGraph > +typedef ::testing::Types, + ForwardEbertGraph > GraphTypesForDebugStringTestWithGraphBuildManager; TYPED_TEST_SUITE(DebugStringTestWithGraphBuildManager, diff --git a/ortools/graph/generic_max_flow.h b/ortools/graph/generic_max_flow.h index c1ad4bba792..0ff7b505831 100644 --- a/ortools/graph/generic_max_flow.h +++ b/ortools/graph/generic_max_flow.h @@ -510,19 +510,6 @@ class GenericMaxFlow : public MaxFlowStatusClass { mutable StatsGroup stats_; }; -#if !SWIG - -// Default instance MaxFlow that uses StarGraph. Note that we cannot just use a -// typedef because of dependent code expecting MaxFlow to be a real class. -// TODO(user): Modify this code and remove it. -class MaxFlow : public GenericMaxFlow { - public: - MaxFlow(const StarGraph* graph, NodeIndex source, NodeIndex target) - : GenericMaxFlow(graph, source, target) {} -}; - -#endif // SWIG - template bool PriorityQueueWithRestrictedPush::IsEmpty() const { diff --git a/ortools/graph/linear_assignment_test.cc b/ortools/graph/linear_assignment_test.cc index 148de43f1d6..e6624e139e2 100644 --- a/ortools/graph/linear_assignment_test.cc +++ b/ortools/graph/linear_assignment_test.cc @@ -103,11 +103,9 @@ class LinearSumAssignmentTestWithGraphBuilder : public ::testing::Test {}; typedef ::testing::Types< EbertGraph, ForwardEbertGraph, - ForwardStaticGraph, EbertGraph, - ForwardEbertGraph, ForwardStaticGraph, + EbertGraph, ForwardEbertGraph, EbertGraph, ForwardEbertGraph, - ForwardStaticGraph, StarGraph, ForwardStarGraph, - util::ListGraph<>, util::ReverseArcListGraph<>> + StarGraph, ForwardStarGraph, util::ListGraph<>, util::ReverseArcListGraph<>> GraphTypesForAssignmentTestingWithGraphBuilder; TYPED_TEST_SUITE(LinearSumAssignmentTestWithGraphBuilder, diff --git a/ortools/graph/min_cost_flow.cc b/ortools/graph/min_cost_flow.cc index 7af5b779490..9e9109462d2 100644 --- a/ortools/graph/min_cost_flow.cc +++ b/ortools/graph/min_cost_flow.cc @@ -21,6 +21,7 @@ #include #include +#include "absl/base/attributes.h" #include "absl/flags/flag.h" #include "absl/log/check.h" #include "absl/strings/str_format.h" @@ -30,8 +31,8 @@ #include "ortools/graph/generic_max_flow.h" #include "ortools/graph/graph.h" #include "ortools/graph/graphs.h" -#include "ortools/graph/max_flow.h" #include "ortools/util/saturated_arithmetic.h" +#include "ortools/util/stats.h" // TODO(user): Remove these flags and expose the parameters in the API. // New clients, please do not use these flags! @@ -360,8 +361,8 @@ bool GenericMinCostFlow -FlowQuantity GenericMinCostFlow::Flow( - ArcIndex arc) const { +auto GenericMinCostFlow::Flow( + ArcIndex arc) const -> FlowQuantity { if (IsArcDirect(arc)) { return residual_arc_capacity_[Opposite(arc)]; } else { @@ -371,9 +372,8 @@ FlowQuantity GenericMinCostFlow::Flow( // We use the equations given in the comment of residual_arc_capacity_. template -FlowQuantity -GenericMinCostFlow::Capacity( - ArcIndex arc) const { +auto GenericMinCostFlow::Capacity( + ArcIndex arc) const -> FlowQuantity { if (IsArcDirect(arc)) { return residual_arc_capacity_[arc] + residual_arc_capacity_[Opposite(arc)]; } else { @@ -382,16 +382,16 @@ GenericMinCostFlow::Capacity( } template -CostValue GenericMinCostFlow::UnitCost( - ArcIndex arc) const { +auto GenericMinCostFlow::UnitCost( + ArcIndex arc) const -> CostValue { DCHECK(IsArcValid(arc)); DCHECK_EQ(uint64_t{1}, cost_scaling_factor_); return scaled_arc_unit_cost_[arc]; } template -FlowQuantity GenericMinCostFlow::Supply( - NodeIndex node) const { +auto GenericMinCostFlow::Supply( + NodeIndex node) const -> FlowQuantity { DCHECK(graph_->IsNodeValid(node)); return node_excess_[node]; } @@ -417,16 +417,14 @@ bool GenericMinCostFlow::IsActive( } template -CostValue -GenericMinCostFlow::ReducedCost( - ArcIndex arc) const { +auto GenericMinCostFlow::ReducedCost( + ArcIndex arc) const -> CostValue { return FastReducedCost(arc, node_potential_[Tail(arc)]); } template -CostValue -GenericMinCostFlow::FastReducedCost( - ArcIndex arc, CostValue tail_potential) const { +auto GenericMinCostFlow::FastReducedCost( + ArcIndex arc, CostValue tail_potential) const -> CostValue { DCHECK_EQ(node_potential_[Tail(arc)], tail_potential); DCHECK(graph_->IsNodeValid(Tail(arc))); DCHECK(graph_->IsNodeValid(Head(arc))); @@ -440,9 +438,8 @@ GenericMinCostFlow::FastReducedCost( } template -typename GenericMinCostFlow::ArcIndex -GenericMinCostFlow:: - GetFirstOutgoingOrOppositeIncomingArc(NodeIndex node) const { +auto GenericMinCostFlow:: + GetFirstOutgoingOrOppositeIncomingArc(NodeIndex node) const -> ArcIndex { OutgoingOrOppositeIncomingArcIterator arc_it(*graph_, node); return arc_it.Index(); } @@ -478,8 +475,8 @@ bool GenericMinCostFlow::Solve() { } template -CostValue -GenericMinCostFlow::GetOptimalCost() { +auto GenericMinCostFlow::GetOptimalCost() + -> CostValue { if (status_ != OPTIMAL) { return 0; } @@ -1034,10 +1031,9 @@ void SimpleMinCostFlow::SetNodeSupply(NodeIndex node, FlowQuantity supply) { node_supply_[node] = supply; } -ArcIndex SimpleMinCostFlow::AddArcWithCapacityAndUnitCost(NodeIndex tail, - NodeIndex head, - FlowQuantity capacity, - CostValue unit_cost) { +SimpleMinCostFlow::ArcIndex SimpleMinCostFlow::AddArcWithCapacityAndUnitCost( + NodeIndex tail, NodeIndex head, FlowQuantity capacity, + CostValue unit_cost) { ResizeNodeVectors(std::max(tail, head)); const ArcIndex arc = arc_tail_.size(); arc_tail_.push_back(tail); @@ -1047,7 +1043,7 @@ ArcIndex SimpleMinCostFlow::AddArcWithCapacityAndUnitCost(NodeIndex tail, return arc; } -ArcIndex SimpleMinCostFlow::PermutedArc(ArcIndex arc) { +SimpleMinCostFlow::ArcIndex SimpleMinCostFlow::PermutedArc(ArcIndex arc) { return arc < arc_permutation_.size() ? arc_permutation_[arc] : arc; } @@ -1171,31 +1167,46 @@ SimpleMinCostFlow::Status SimpleMinCostFlow::SolveWithPossibleAdjustment( return min_cost_flow.status(); } -CostValue SimpleMinCostFlow::OptimalCost() const { return optimal_cost_; } +SimpleMinCostFlow::CostValue SimpleMinCostFlow::OptimalCost() const { + return optimal_cost_; +} -FlowQuantity SimpleMinCostFlow::MaximumFlow() const { return maximum_flow_; } +SimpleMinCostFlow::FlowQuantity SimpleMinCostFlow::MaximumFlow() const { + return maximum_flow_; +} -FlowQuantity SimpleMinCostFlow::Flow(ArcIndex arc) const { +SimpleMinCostFlow::FlowQuantity SimpleMinCostFlow::Flow(ArcIndex arc) const { return arc_flow_[arc]; } -NodeIndex SimpleMinCostFlow::NumNodes() const { return node_supply_.size(); } +SimpleMinCostFlow::SimpleMinCostFlow::NodeIndex SimpleMinCostFlow::NumNodes() + const { + return node_supply_.size(); +} -ArcIndex SimpleMinCostFlow::NumArcs() const { return arc_tail_.size(); } +SimpleMinCostFlow::ArcIndex SimpleMinCostFlow::NumArcs() const { + return arc_tail_.size(); +} -ArcIndex SimpleMinCostFlow::Tail(ArcIndex arc) const { return arc_tail_[arc]; } +SimpleMinCostFlow::ArcIndex SimpleMinCostFlow::Tail(ArcIndex arc) const { + return arc_tail_[arc]; +} -ArcIndex SimpleMinCostFlow::Head(ArcIndex arc) const { return arc_head_[arc]; } +SimpleMinCostFlow::ArcIndex SimpleMinCostFlow::Head(ArcIndex arc) const { + return arc_head_[arc]; +} -FlowQuantity SimpleMinCostFlow::Capacity(ArcIndex arc) const { +SimpleMinCostFlow::FlowQuantity SimpleMinCostFlow::Capacity( + ArcIndex arc) const { return arc_capacity_[arc]; } -CostValue SimpleMinCostFlow::UnitCost(ArcIndex arc) const { +SimpleMinCostFlow::CostValue SimpleMinCostFlow::UnitCost(ArcIndex arc) const { return arc_cost_[arc]; } -FlowQuantity SimpleMinCostFlow::Supply(NodeIndex node) const { +SimpleMinCostFlow::FlowQuantity SimpleMinCostFlow::Supply( + NodeIndex node) const { return node_supply_[node]; } diff --git a/ortools/graph/samples/simple_min_cost_flow_program.cc b/ortools/graph/samples/simple_min_cost_flow_program.cc index 9e5f6c154a0..637a8069796 100644 --- a/ortools/graph/samples/simple_min_cost_flow_program.cc +++ b/ortools/graph/samples/simple_min_cost_flow_program.cc @@ -14,6 +14,7 @@ // [START program] // From Bradley, Hax and Maganti, 'Applied Mathematical Programming', figure 8.1 // [START import] +#include #include #include diff --git a/ortools/graph/shortest_paths.h b/ortools/graph/shortest_paths.h index 787c273cb91..ceb9ebd85d6 100644 --- a/ortools/graph/shortest_paths.h +++ b/ortools/graph/shortest_paths.h @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file contains functions to compute shortest paths on Ebert graphs using +// This file contains functions to compute shortest paths on graphs using // Dijkstra's algorithm, // E.W. Dijkstra, "A note on two problems in connexion with graphs". Numerische // Mathematik 1:269–271, 1959. See for example: @@ -65,13 +65,21 @@ #include #include #include +#include #include +#include "absl/base/attributes.h" +#include "absl/container/flat_hash_map.h" +#include "absl/functional/bind_front.h" #include "absl/log/check.h" +#include "absl/types/span.h" +#include "ortools/base/adjustable_priority_queue-inl.h" +#include "ortools/base/adjustable_priority_queue.h" #include "ortools/base/logging.h" -#include "ortools/graph/ebert_graph.h" -#include "ortools/graph/graph.h" - +#include "ortools/base/map_util.h" +#include "ortools/base/stl_util.h" +#include "ortools/base/threadpool.h" +#include "ortools/base/timer.h" namespace operations_research { // Storing distances on 32 bits to limit memory consumption of distance @@ -82,82 +90,112 @@ typedef uint32_t PathDistance; const PathDistance kDisconnectedPathDistance = std::numeric_limits::max(); +namespace internal { +template class PathContainerImpl; +} // namespace internal // Container class storing paths and distances along the paths. It is used in // shortest path computation functions to store resulting shortest paths. -// Usage example iterating on the path between nodes from and to: +// Usage example iterating on the path between nodes `from` and `to`: // PathContainer path_container; // PathContainer::BuildInMemoryCompactPathContainer(&path_container); -// ... fill up container ... -// const NodeIndex from =...; -// NodeIndex to =...; +// // ... fill up container ... +// const PathContainer::NodeIndex from =...; +// PathContainer::NodeIndex to =...; // while (to != from) { // LOG(INFO) << to; // to = path_container.GetPenultimateNodeInPath(from, to); // } -class PathContainer { +template +class GenericPathContainer { public: - PathContainer(); + using NodeIndex = typename GraphType::NodeIndex; + using Impl = internal::PathContainerImpl; + + // TODO(b/385094969): Remove this when all clients are migrated, and use + // factory functions instead. + GenericPathContainer(); // This type is neither copyable nor movable. - PathContainer(const PathContainer&) = delete; - PathContainer& operator=(const PathContainer&) = delete; + GenericPathContainer(const GenericPathContainer&) = delete; + GenericPathContainer& operator=(const GenericPathContainer&) = delete; - ~PathContainer(); + ~GenericPathContainer(); - // Returns the distance between node 'from' and node 'to' following the path - // out of 'from' and into 'to'. Note that if 'from' == 'to', the distance is - // not necessarily 0 if the path out of 'to' and back into 'to' has a distance + // Returns the distance between node `from` and node `to` following the path + // out of `from` and into `to`. Note that if `from` == `to`, the distance is + // not necessarily 0 if the path out of `to` and back into `to` has a distance // greater than 0. If you do require the distance to be 0 in this case, add to - // the graph an arc from 'to' to itself with a length of 0. - // If nodes are not connected, returns kDisconnectedPathDistance. + // the graph an arc from `to` to itself with a length of 0. + // If nodes are not connected, returns `kDisconnectedPathDistance`. PathDistance GetDistance(NodeIndex from, NodeIndex to) const; - // Returns the penultimate node on the path out of node 'from' into node 'to' - // (the direct predecessor of node 'to' on the path). - // If 'from' == 'to', the penultimate node is 'to' only if the shortest path - // from 'to' to itself is composed of the arc ('to, 'to'), which might not be + // Returns the penultimate node on the path out of node `from` into node `to` + // (the direct predecessor of node `to` on the path). + // If `from` == `to`, the penultimate node is `to` only if the shortest path + // from `to` to itself is composed of the arc (`to, `to`), which might not be // the case if either this arc doesn't exist or if the length of this arc is // greater than the distance of an alternate path. - // If nodes are not connected, returns StarGraph::kNilNode. + // If nodes are not connected, returns `GraphType::kNilNode`. NodeIndex GetPenultimateNodeInPath(NodeIndex from, NodeIndex to) const; - // Returns path nodes from node "from" to node "to" in a ordered vector. - // The vector starts with 'from' and ends with 'to', if both nodes are + // Returns path nodes from node `from` to node `to` in the order in which they + // appear along the path. + // The vector starts with `from` and ends with `to`, if both nodes are // connected (otherwise an empty vector is returned). void GetPath(NodeIndex from, NodeIndex to, std::vector* path) const; - // For internal use only. Returns the internal container implementation. - PathContainerImpl* GetImplementation() const; - // Builds a path container which only stores distances between path nodes. - static void BuildPathDistanceContainer(PathContainer* path_container); + static GenericPathContainer BuildPathDistanceContainer(); + + ABSL_DEPRECATED("Use factory function BuildPathDistanceContainer instead.") + static void BuildPathDistanceContainer(GenericPathContainer* path_container); // Builds a path container which stores explicit paths and distances between // path nodes in a memory-compact representation. - // In this case GetPenultimateNodeInPath() is O(log(path_tree_size)), - // path_tree_size being the size of a tree of paths from a source node (in + // In this case `GetPenultimateNodeInPath()` is `O(log(path_tree_size))`, + // `path_tree_size` being the size of a tree of paths from a source node (in // practice it is equal to the number of nodes in the graph if all nodes // are strongly connected). - // GetPath is O(log(path_tree_size) + path_size), where path_size is the + // `GetPath` is `O(log(path_tree_size) + path_size)`, where `path_size` is the // size of the resulting path; note this is faster than successive calls - // to GetPenultimateNodeInPath() which would result in - // O(log(path_tree_size) * path_size). - static void BuildInMemoryCompactPathContainer(PathContainer* path_container); + // to `GetPenultimateNodeInPath()` which would result in + // `O(log(path_tree_size) * path_size)`. + static GenericPathContainer BuildInMemoryCompactPathContainer(); + + ABSL_DEPRECATED( + "Use factory function BuildInMemoryCompactPathContainer instead.") + static void BuildInMemoryCompactPathContainer( + GenericPathContainer* path_container); // TODO(user): Add save-to-disk container. - // TODO(user): Add BuildInMemoryFastPathContainer(), which does - // GetPenultimateNodeInPath() in O(1). + // TODO(user): Add `BuildInMemoryFastPathContainer()`, which does + // `GetPenultimateNodeInPath()` in `O(1)`. + + // For internal use only. Returns the internal container implementation. + Impl* GetImplementation() const { return container_.get(); } private: - std::unique_ptr container_; + explicit GenericPathContainer(std::unique_ptr impl) + : container_(std::move(impl)) {} + + std::unique_ptr container_; }; +// TODO(b/385094969): Remove this alias when all clients are migrated. +class LegacyIgnoredGraphType { + public: + using NodeIndex = int32_t; + static constexpr NodeIndex kNilNode = std::numeric_limits::max(); +}; +using PathContainer = GenericPathContainer; + // Utility function which returns a vector containing all nodes of a graph. template -void GetGraphNodes(const GraphType& graph, std::vector* nodes) { +void GetGraphNodes(const GraphType& graph, + std::vector* nodes) { CHECK(nodes != nullptr); nodes->clear(); nodes->reserve(graph.num_nodes()); @@ -179,41 +217,43 @@ void GetGraphNodesFromGraph(const GraphType& graph, } // In all the functions below the arc_lengths vector represents the lengths of -// the arcs of the graph (arc_lengths[arc] is the length of arc). -// Resulting shortest paths are stored in a path container 'path_container'. +// the arcs of the graph (`arc_lengths[arc]` is the length of `arc`). +// Resulting shortest paths are stored in a path container `path_container`. -// Computes shortest paths from the node 'source' to all nodes in the graph. +// Computes shortest paths from the node `source` to all nodes in the graph. template -void ComputeOneToAllShortestPaths(const GraphType& graph, - const std::vector& arc_lengths, - typename GraphType::NodeIndex source, - PathContainer* const path_container) { +void ComputeOneToAllShortestPaths( + const GraphType& graph, const std::vector& arc_lengths, + typename GraphType::NodeIndex source, + GenericPathContainer* const path_container) { std::vector all_nodes; GetGraphNodesFromGraph(graph, &all_nodes); ComputeOneToManyShortestPaths(graph, arc_lengths, source, all_nodes, path_container); } -// Computes shortest paths from the node 'source' to nodes in 'destinations'. -template +// Computes shortest paths from the node `source` to nodes in `destinations`. +// TODO(b/385094969): Remove second template parameter when all clients are +// migrated. +template void ComputeOneToManyShortestPaths( const GraphType& graph, const std::vector& arc_lengths, typename GraphType::NodeIndex source, const std::vector& destinations, - PathContainer* const path_container) { + GenericPathContainer* const path_container) { std::vector sources(1, source); ComputeManyToManyShortestPathsWithMultipleThreads( graph, arc_lengths, sources, destinations, 1, path_container); } -// Computes the shortest path from the node 'source' to the node 'destination' -// and returns that path as a vector of nodes. If there is no path from 'source' -// to 'destination', the returned vector is empty. +// Computes the shortest path from the node `source` to the node `destination` +// and returns that path as a vector of nodes. If there is no path from `source` +// to `destination`, the returned vector is empty. // -// To get distance information, use ComputeOneToManyShortestPaths with a single -// destination and a `PathContainer` built with `BuildPathDistanceContainer` (if -// you just need the distance) or `BuildInMemoryCompactPathContainer` -// (otherwise). +// To get distance information, use `ComputeOneToManyShortestPaths` with a +// single destination and a `PathContainer` built with +// `BuildPathDistanceContainer` (if you just need the distance) or +// `BuildInMemoryCompactPathContainer` (otherwise). template std::vector ComputeOneToOneShortestPath( const GraphType& graph, const std::vector& arc_lengths, @@ -222,8 +262,8 @@ std::vector ComputeOneToOneShortestPath( std::vector sources(1, source); std::vector destinations(1, destination); - PathContainer path_container; - PathContainer::BuildInMemoryCompactPathContainer(&path_container); + auto path_container = + GenericPathContainer::BuildInMemoryCompactPathContainer(); ComputeManyToManyShortestPathsWithMultipleThreads( graph, arc_lengths, sources, destinations, 1, &path_container); @@ -233,91 +273,559 @@ std::vector ComputeOneToOneShortestPath( return path; } -// Computes shortest paths from the nodes in 'sources' to all nodes in the +// Computes shortest paths from the nodes in `sources` to all nodes in the // graph. template void ComputeManyToAllShortestPathsWithMultipleThreads( const GraphType& graph, const std::vector& arc_lengths, const std::vector& sources, int num_threads, - PathContainer* const path_container) { + GenericPathContainer* const path_container) { std::vector all_nodes; GetGraphNodesFromGraph(graph, &all_nodes); ComputeManyToManyShortestPathsWithMultipleThreads( graph, arc_lengths, sources, all_nodes, num_threads, path_container); } -// Computes shortest paths from the nodes in 'sources' to the nodes in -// 'destinations'. -template -void ComputeManyToManyShortestPathsWithMultipleThreads( +// Computes shortest paths between all nodes of the graph. +// TODO(b/385094969): Remove second template parameter when all clients are +// migrated. +template +void ComputeAllToAllShortestPathsWithMultipleThreads( const GraphType& graph, const std::vector& arc_lengths, - const std::vector& sources, - const std::vector& destinations, - int num_threads, PathContainer* const path_container) { - (void)graph; - (void)arc_lengths; - (void)sources; - (void)destinations; - (void)num_threads; - (void)path_container; - - LOG(DFATAL) << "Graph type not supported"; + int num_threads, + GenericPathContainer* const path_container) { + std::vector all_nodes; + GetGraphNodesFromGraph(graph, &all_nodes); + ComputeManyToManyShortestPathsWithMultipleThreads( + graph, arc_lengths, all_nodes, all_nodes, num_threads, path_container); } -// Specialization for supported graph classes. +// ============================================================================= +// Implementation. +// ============================================================================= -using ::util::ListGraph; -template <> -void ComputeManyToManyShortestPathsWithMultipleThreads( - const ListGraph<>& graph, const std::vector& arc_lengths, - const std::vector::NodeIndex>& sources, - const std::vector::NodeIndex>& destinations, int num_threads, - PathContainer* path_container); +namespace internal { -using ::util::StaticGraph; -template <> -void ComputeManyToManyShortestPathsWithMultipleThreads( - const StaticGraph<>& graph, const std::vector& arc_lengths, - const std::vector::NodeIndex>& sources, - const std::vector::NodeIndex>& destinations, int num_threads, - PathContainer* path_container); +// Base path container implementation class. Defines virtual functions used to +// fill the container (in particular from the shortest path computation +// function). +template +class PathContainerImpl { + public: + PathContainerImpl() = default; + virtual ~PathContainerImpl() = default; + + // Initializes the container on source and destination node vectors + // (`num_nodes` is the total number of nodes in the graph containing source + // and destination nodes). + // Called before adding any paths to the container. + virtual void Initialize(const std::vector& sources, + const std::vector& destinations, + NodeIndex num_nodes) = 0; + + // Called when no more path will be added to the container. + virtual void Finalize() {} + + // Returns the distance between node `from` and node `to` following the path + // out of `from` and into `to`. Note that if `from` == `to`, the distance is + // not necessarily 0 if the path out of `to` and back into `to` has a distance + // greater than 0. If you do require the distance to be 0 in this case, add to + // the graph an arc from `to` to itself with a length of 0. + // If nodes are not connected, returns `kDisconnectedPathDistance`. + virtual PathDistance GetDistance(NodeIndex from, NodeIndex to) const = 0; + + // Returns the penultimate node on the path out of node `from` into node `to` + // (the direct predecessor of node `to` on the path). + // If `from` == `to`, the penultimate node is `to` only if the shortest path + // from `to` to itself is composed of the arc (`to, `to`), which might not be + // the case if either this arc doesn't exist or if the length of this arc is + // greater than the distance of an alternate path. + // If nodes are not connected, returns `kNilNode`. + virtual NodeIndex GetPenultimateNodeInPath(NodeIndex from, + NodeIndex to) const = 0; + + // Returns path nodes from node `from` to node `to` in a ordered vector. + virtual void GetPath(NodeIndex from, NodeIndex to, + std::vector* path) const = 0; + + // Adds a path tree rooted at node `from`, and to a set of implicit + // destinations: + // - `predecessor_in_path_tree[node]` is the predecessor of node `node` in the + // path from `from` to `node`, or `kNilNode` if there is no + // predecessor (i.e. if `node` is not in the path tree); + // - `distance_to_destination[i]` is the distance from `from` to the i-th + // destination (see `Initialize()`). + virtual void StoreSingleSourcePaths( + NodeIndex from, const std::vector& predecessor_in_path_tree, + const std::vector& distance_to_destination) = 0; +}; -using ::util::ReverseArcListGraph; -template <> -void ComputeManyToManyShortestPathsWithMultipleThreads( - const ReverseArcListGraph<>& graph, - const std::vector& arc_lengths, - const std::vector::NodeIndex>& sources, - const std::vector::NodeIndex>& destinations, - int num_threads, PathContainer* path_container); - -using ::util::ReverseArcStaticGraph; -template <> -void ComputeManyToManyShortestPathsWithMultipleThreads( - const ReverseArcStaticGraph<>& graph, - const std::vector& arc_lengths, - const std::vector::NodeIndex>& sources, - const std::vector::NodeIndex>& destinations, - int num_threads, PathContainer* path_container); - -using ::util::ReverseArcMixedGraph; -template <> -void ComputeManyToManyShortestPathsWithMultipleThreads( - const ReverseArcMixedGraph<>& graph, - const std::vector& arc_lengths, - const std::vector::NodeIndex>& sources, - const std::vector::NodeIndex>& destinations, - int num_threads, PathContainer* path_container); +// Class designed to store the tree of paths from a root node to a set of nodes +// in a very compact way (over performance). +// Memory consumption is in `O(n)` (`n` being the size of the tree) where node +// indices are "very" non-contiguous (extremely sparse node indices). It keeps +// node-sorted arrays of node and parent pairs, which can be accessed in +// `O(log(n))` with a binary search. +// The creation of the tree is done in `O(n*log(n))` time. +// Note that this class uses temporary memory for each call to `Initialize` +// which is only an issue for massive parallel calls; in practice for shortest +// paths computation, the number of threads calling `Initialize` is very small +// compared to the total number of trees created. +template +class PathTree { + public: + PathTree() : nodes_(), parents_() {} + + void Initialize(absl::Span paths, + absl::Span destinations); + + // Returns the parent (predecessor) of `node` in the tree in + // `O(log(path_tree_size))`, where `path_tree_size` is the size of `nodes_`. + NodeIndex GetParent(NodeIndex node) const; + + // Returns the path from node `from` to node `to` in the tree in + // `O(log(path_tree_size) + path_size)`, where `path_tree_size` is the size of + // `nodes_` and `path_size` is the size of the resulting path. + void GetPath(NodeIndex from, NodeIndex to, + std::vector* path) const; + + private: + std::vector nodes_; + std::vector parents_; +}; + +// Initializes the tree from a non-sparse representation of the path tree +// represented by `paths`. The tree is reduced to the subtree in which nodes in +// `destinations` are the leafs. +template +void PathTree::Initialize( + absl::Span paths, + absl::Span destinations) { + std::vector node_explored(paths.size(), false); + const int destination_size = destinations.size(); + typedef std::pair NodeParent; + std::vector tree; + for (int i = 0; i < destination_size; ++i) { + NodeIndex destination = destinations[i]; + while (!node_explored[destination]) { + node_explored[destination] = true; + tree.push_back(std::make_pair(destination, paths[destination])); + if (paths[destination] != kNilNode) { + destination = paths[destination]; + } + } + } + std::sort(tree.begin(), tree.end()); + const int num_nodes = tree.size(); + { + absl::flat_hash_map node_indices; + + for (int i = 0; i < num_nodes; ++i) { + node_indices[tree[i].first] = i; + } + parents_.resize(num_nodes, -1); + for (int i = 0; i < num_nodes; ++i) { + parents_[i] = + ::gtl::FindWithDefault(node_indices, tree[i].second, kNilNode); + } + } + nodes_.resize(num_nodes, kNilNode); + for (int i = 0; i < num_nodes; ++i) { + nodes_[i] = tree[i].first; + } +} + +template +NodeIndex PathTree::GetParent(NodeIndex node) const { + const auto node_position = absl::c_lower_bound(nodes_, node); + if (node_position != nodes_.end() && *node_position == node) { + const int parent = parents_[node_position - nodes_.begin()]; + if (parent != kNilNode) { + return nodes_[parent]; + } + } + return kNilNode; +} + +template +void PathTree::GetPath( + NodeIndex from, NodeIndex to, std::vector* path) const { + DCHECK(path != nullptr); + path->clear(); + const auto to_position = absl::c_lower_bound(nodes_, to); + if (to_position != nodes_.end() && *to_position == to) { + int current_index = to_position - nodes_.begin(); + NodeIndex current_node = to; + while (current_node != from) { + path->push_back(current_node); + current_index = parents_[current_index]; + // `from` and `to` are not connected. + if (current_index == kNilNode) { + path->clear(); + return; + } + current_node = nodes_[current_index]; + } + path->push_back(current_node); + std::reverse(path->begin(), path->end()); + } +} + +// Path container which only stores distances between path nodes. +template +class DistanceContainer : public PathContainerImpl { + public: + DistanceContainer() : reverse_sources_(), distances_() {} + + // This type is neither copyable nor movable. + DistanceContainer(const DistanceContainer&) = delete; + DistanceContainer& operator=(const DistanceContainer&) = delete; + ~DistanceContainer() override = default; + void Initialize(const std::vector& sources, + const std::vector& destinations, + NodeIndex num_nodes) override { + ComputeReverse(sources, num_nodes, &reverse_sources_); + ComputeReverse(destinations, num_nodes, &reverse_destinations_); + distances_.clear(); + distances_.resize(sources.size()); + } + PathDistance GetDistance(NodeIndex from, NodeIndex to) const override { + return distances_[reverse_sources_[from]][reverse_destinations_[to]]; + } + NodeIndex GetPenultimateNodeInPath(NodeIndex, NodeIndex) const override { + LOG(FATAL) << "Path not stored."; + return kNilNode; + } + void GetPath(NodeIndex, NodeIndex, std::vector*) const override { + LOG(FATAL) << "Path not stored."; + } + void StoreSingleSourcePaths( + NodeIndex from, + // `DistanceContainer` only stores distances and not predecessors. + const std::vector&, + const std::vector& distance_to_destination) override { + distances_[reverse_sources_[from]] = distance_to_destination; + } + + protected: + std::vector reverse_sources_; + std::vector reverse_destinations_; + + private: + static void ComputeReverse(absl::Span nodes, + NodeIndex num_nodes, + std::vector* reverse_nodes) { + CHECK(reverse_nodes != nullptr); + const int kUnassignedIndex = -1; + reverse_nodes->clear(); + reverse_nodes->resize(num_nodes, kUnassignedIndex); + for (int i = 0; i < nodes.size(); ++i) { + reverse_nodes->at(nodes[i]) = i; + } + } + + std::vector> distances_; +}; + +// Path container which stores explicit paths and distances between path nodes. +template +class InMemoryCompactPathContainer + : public DistanceContainer { + public: + using Base = DistanceContainer; + + InMemoryCompactPathContainer() : trees_(), destinations_() {} + + // This type is neither copyable nor movable. + InMemoryCompactPathContainer(const InMemoryCompactPathContainer&) = delete; + InMemoryCompactPathContainer& operator=(const InMemoryCompactPathContainer&) = + delete; + ~InMemoryCompactPathContainer() override = default; + void Initialize(const std::vector& sources, + const std::vector& destinations, + NodeIndex num_nodes) override { + Base::Initialize(sources, destinations, num_nodes); + destinations_ = destinations; + trees_.clear(); + trees_.resize(sources.size()); + } + NodeIndex GetPenultimateNodeInPath(NodeIndex from, + NodeIndex to) const override { + return trees_[Base::reverse_sources_[from]].GetParent(to); + } + void GetPath(NodeIndex from, NodeIndex to, + std::vector* path) const override { + DCHECK(path != nullptr); + trees_[Base::reverse_sources_[from]].GetPath(from, to, path); + } + void StoreSingleSourcePaths( + NodeIndex from, const std::vector& predecessor_in_path_tree, + const std::vector& distance_to_destination) override { + Base::StoreSingleSourcePaths(from, predecessor_in_path_tree, + distance_to_destination); + trees_[Base::reverse_sources_[from]].Initialize(predecessor_in_path_tree, + destinations_); + } + + private: + std::vector> trees_; + std::vector destinations_; +}; + +// Priority queue node entry in the boundary of the Dijkstra algorithm. +template +class NodeEntry { + public: + NodeEntry() + : heap_index_(-1), + distance_(0), + node_(kNilNode), + settled_(false), + is_destination_(false) {} + bool operator<(const NodeEntry& other) const { + return distance_ > other.distance_; + } + void SetHeapIndex(int h) { + DCHECK_GE(h, 0); + heap_index_ = h; + } + int GetHeapIndex() const { return heap_index_; } + void set_distance(PathDistance distance) { distance_ = distance; } + PathDistance distance() const { return distance_; } + void set_node(NodeIndex node) { node_ = node; } + NodeIndex node() const { return node_; } + void set_settled(bool settled) { settled_ = settled; } + bool settled() const { return settled_; } + void set_is_destination(bool is_destination) { + is_destination_ = is_destination; + } + bool is_destination() const { return is_destination_; } + + private: + int heap_index_; + PathDistance distance_; + NodeIndex node_; + bool settled_; + bool is_destination_; +}; + +// Updates an entry with the given distance if it's shorter, and then inserts it +// in the priority queue (or updates it if it's there already), if needed. +// Returns true if the entry was modified, false otherwise. +template +bool InsertOrUpdateEntry( + PathDistance distance, NodeEntry* entry, + AdjustablePriorityQueue>* priority_queue) { + // If one wants to use int64_t for either priority or NodeIndex, one should + // consider using packed ints (putting the two bools with heap_index, for + // example) in order to stay at 16 bytes instead of 24. + static_assert(sizeof(NodeEntry) == 16, + "node_entry_class_is_not_well_packed"); + + DCHECK(priority_queue != nullptr); + DCHECK(entry != nullptr); + if (!priority_queue->Contains(entry)) { + entry->set_distance(distance); + priority_queue->Add(entry); + return true; + } else if (distance < entry->distance()) { + entry->set_distance(distance); + priority_queue->NoteChangedPriority(entry); + return true; + } + return false; +} + +// Computes shortest paths from node `source` to nodes in `destinations` +// using a binary heap-based Dijkstra algorithm. +// TODO(user): Investigate alternate implementation which wouldn't use +// AdjustablePriorityQueue. +// TODO(b/385094969): Remove second template parameter when all clients are +// migrated. +template +void ComputeOneToManyOnGraph( + const GraphType* const graph, + const std::vector* const arc_lengths, + typename GraphType::NodeIndex source, + const std::vector* const destinations, + typename GenericPathContainer::Impl* const paths) { + using NodeIndex = typename GraphType::NodeIndex; + using ArcIndex = typename GraphType::ArcIndex; + using NodeEntryT = NodeEntry; + CHECK(graph != nullptr); + CHECK(arc_lengths != nullptr); + CHECK(destinations != nullptr); + CHECK(paths != nullptr); + const int num_nodes = graph->num_nodes(); + std::vector predecessor(num_nodes, GraphType::kNilNode); + AdjustablePriorityQueue priority_queue; + std::vector entries(num_nodes); + for (const NodeIndex node : graph->AllNodes()) { + entries[node].set_node(node); + } + // Marking destination node. This is an optimization stopping the search + // when all destinations have been reached. + for (int i = 0; i < destinations->size(); ++i) { + entries[(*destinations)[i]].set_is_destination(true); + } + // In this implementation the distance of a node to itself isn't necessarily + // 0. + // So we push successors of source in the queue instead of the source + // directly which will avoid marking the source. + for (const ArcIndex arc : graph->OutgoingArcs(source)) { + const NodeIndex next = graph->Head(arc); + if (InsertOrUpdateEntry(arc_lengths->at(arc), &entries[next], + &priority_queue)) { + predecessor[next] = source; + } + } + int destinations_remaining = destinations->size(); + while (!priority_queue.IsEmpty()) { + NodeEntryT* current = priority_queue.Top(); + const NodeIndex current_node = current->node(); + priority_queue.Pop(); + current->set_settled(true); + if (current->is_destination()) { + destinations_remaining--; + if (destinations_remaining == 0) { + break; + } + } + const PathDistance current_distance = current->distance(); + for (const ArcIndex arc : graph->OutgoingArcs(current_node)) { + const NodeIndex next = graph->Head(arc); + NodeEntryT* const entry = &entries[next]; + if (!entry->settled()) { + DCHECK_GE(current_distance, 0); + const PathDistance arc_length = arc_lengths->at(arc); + DCHECK_LE(current_distance, kDisconnectedPathDistance - arc_length); + if (InsertOrUpdateEntry(current_distance + arc_length, entry, + &priority_queue)) { + predecessor[next] = current_node; + } + } + } + } + const int destinations_size = destinations->size(); + std::vector distances(destinations_size, + kDisconnectedPathDistance); + for (int i = 0; i < destinations_size; ++i) { + NodeIndex node = destinations->at(i); + if (entries[node].settled()) { + distances[i] = entries[node].distance(); + } + } + paths->StoreSingleSourcePaths(source, predecessor, distances); +} + +} // namespace internal -// Computes shortest paths between all nodes of the graph. template -void ComputeAllToAllShortestPathsWithMultipleThreads( +GenericPathContainer::GenericPathContainer() = default; + +template +GenericPathContainer::~GenericPathContainer() = default; + +template +PathDistance GenericPathContainer::GetDistance(NodeIndex from, + NodeIndex to) const { + DCHECK(container_ != nullptr); + return container_->GetDistance(from, to); +} + +template +typename GenericPathContainer::NodeIndex +GenericPathContainer::GetPenultimateNodeInPath(NodeIndex from, + NodeIndex to) const { + DCHECK(container_ != nullptr); + return container_->GetPenultimateNodeInPath(from, to); +} + +template +void GenericPathContainer::GetPath( + NodeIndex from, NodeIndex to, std::vector* path) const { + DCHECK(container_ != nullptr); + DCHECK(path != nullptr); + container_->GetPath(from, to, path); +} + +template +void GenericPathContainer::BuildPathDistanceContainer( + GenericPathContainer* const path_container) { + CHECK(path_container != nullptr); + path_container->container_ = std::make_unique< + internal::DistanceContainer>(); +} + +template +void GenericPathContainer::BuildInMemoryCompactPathContainer( + GenericPathContainer* const path_container) { + CHECK(path_container != nullptr); + path_container->container_ = std::make_unique< + internal::InMemoryCompactPathContainer>(); +} + +template +GenericPathContainer +GenericPathContainer::BuildPathDistanceContainer() { + return GenericPathContainer( + std::make_unique< + internal::DistanceContainer>()); +} + +template +GenericPathContainer +GenericPathContainer::BuildInMemoryCompactPathContainer() { + return GenericPathContainer( + std::make_unique>()); +} + +// TODO(b/385094969): Remove second template parameter when all clients are +// migrated. +template +void ComputeManyToManyShortestPathsWithMultipleThreads( const GraphType& graph, const std::vector& arc_lengths, - int num_threads, PathContainer* const path_container) { - std::vector all_nodes; - GetGraphNodesFromGraph(graph, &all_nodes); - ComputeManyToManyShortestPathsWithMultipleThreads( - graph, arc_lengths, all_nodes, all_nodes, num_threads, path_container); + const std::vector& sources, + const std::vector& destinations, + int num_threads, + GenericPathContainer* const paths) { + static_assert(std::is_same_v, + "use an explicit `GenericPathContainer` instead of using " + "`PathContainer`"); + static_assert(GraphType::kNilNode == PathContainerGraphType::kNilNode, + "use an explicit `GenericPathContainer` instead of using " + "`PathContainer`"); + if (graph.num_nodes() > 0) { + CHECK_EQ(graph.num_arcs(), arc_lengths.size()) + << "Number of arcs in graph must match arc length vector size"; + // Removing duplicate sources to allow mutex-free implementation (and it's + // more efficient); same with destinations for efficiency reasons. + std::vector unique_sources = sources; + ::gtl::STLSortAndRemoveDuplicates(&unique_sources); + std::vector unique_destinations = + destinations; + ::gtl::STLSortAndRemoveDuplicates(&unique_destinations); + WallTimer timer; + timer.Start(); + auto* const container = paths->GetImplementation(); + container->Initialize(unique_sources, unique_destinations, + graph.num_nodes()); + { + std::unique_ptr pool(new ThreadPool(num_threads)); + pool->StartWorkers(); + for (int i = 0; i < unique_sources.size(); ++i) { + pool->Schedule(absl::bind_front( + &internal::ComputeOneToManyOnGraph, + &graph, &arc_lengths, unique_sources[i], &unique_destinations, + container)); + } + } + container->Finalize(); + VLOG(2) << "Elapsed time to compute shortest paths: " << timer.Get() << "s"; + } } } // namespace operations_research diff --git a/ortools/graph/shortest_paths_benchmarks.cc b/ortools/graph/shortest_paths_benchmarks.cc index 44b13f1d850..3c3e1d7ee50 100644 --- a/ortools/graph/shortest_paths_benchmarks.cc +++ b/ortools/graph/shortest_paths_benchmarks.cc @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -18,21 +19,20 @@ #include #include -#include "absl/memory/memory.h" #include "absl/random/distributions.h" #include "benchmark/benchmark.h" -#include "gtest/gtest.h" #include "isp/fiber/auto_design/utils/parallelizer.h" -#include "ortools/base/gmock.h" +#include "ortools/base/logging.h" #include "ortools/base/threadlocal.h" #include "ortools/graph/bounded_dijkstra.h" +#include "ortools/graph/graph.h" #include "ortools/graph/shortest_paths.h" #include "ortools/graph/test_util.h" namespace operations_research { namespace { -using Graph = StaticGraph<>; +using Graph = ::util::StaticGraph<>; enum Implementation { BOUNDED_DIJKSTRA = 1, @@ -106,8 +106,8 @@ std::vector> ManyToManyShortestPaths( const Graph& graph, const std::vector& arc_costs, const std::vector& srcs, const std::vector& dsts, int num_threads) { - PathContainer path_container; - PathContainer::BuildPathDistanceContainer(&path_container); + auto path_container = + GenericPathContainer::BuildPathDistanceContainer(); ComputeManyToManyShortestPathsWithMultipleThreads( graph, arc_costs, srcs, dsts, num_threads, &path_container); std::vector> distances( diff --git a/ortools/graph/shortest_paths_test.cc b/ortools/graph/shortest_paths_test.cc index ffc00e6770d..df7bd1d785b 100644 --- a/ortools/graph/shortest_paths_test.cc +++ b/ortools/graph/shortest_paths_test.cc @@ -22,16 +22,17 @@ #include "absl/random/random.h" #include "gtest/gtest.h" #include "ortools/graph/ebert_graph.h" +#include "ortools/graph/graph.h" #include "ortools/graph/strongly_connected_components.h" -#include "ortools/util/zvector.h" namespace operations_research { -void CheckPathDataPair(const PathContainer& container, - const PathContainer& distance_container, - PathDistance expected_distance, - NodeIndex expected_predecessor, NodeIndex tail, - NodeIndex head) { +template +void CheckPathDataPair( + const GenericPathContainer& container, + const GenericPathContainer& distance_container, + PathDistance expected_distance, NodeIndex expected_predecessor, + NodeIndex tail, NodeIndex head) { EXPECT_EQ(expected_distance, container.GetDistance(tail, head)); EXPECT_EQ(expected_distance, distance_container.GetDistance(tail, head)); EXPECT_EQ(expected_predecessor, @@ -59,8 +60,9 @@ void CheckPathDataPair(const PathContainer& container, } template -void CheckPathDataRow(const GraphType& graph, const PathContainer& container, - const PathContainer& distance_container, +void CheckPathDataRow(const GraphType& graph, + const GenericPathContainer& container, + const GenericPathContainer& distance_container, const NodeIndex expected_paths[], const PathDistance expected_distances[], NodeIndex tail) { int index = tail * graph.num_nodes(); @@ -74,12 +76,11 @@ void CheckPathDataRow(const GraphType& graph, const PathContainer& container, } template -void CheckPathDataRowFromGraph(const GraphType& graph, - const PathContainer& container, - const PathContainer& distance_container, - const NodeIndex expected_paths[], - const PathDistance expected_distances[], - NodeIndex tail) { +void CheckPathDataRowFromGraph( + const GraphType& graph, const GenericPathContainer& container, + const GenericPathContainer& distance_container, + const NodeIndex expected_paths[], const PathDistance expected_distances[], + NodeIndex tail) { int index = tail * graph.num_nodes(); for (typename GraphType::NodeIndex head : graph.AllNodes()) { CheckPathDataPair(container, distance_container, expected_distances[index], @@ -89,8 +90,9 @@ void CheckPathDataRowFromGraph(const GraphType& graph, } template -void CheckPathData(const GraphType& graph, const PathContainer& container, - const PathContainer& distance_container, +void CheckPathData(const GraphType& graph, + const GenericPathContainer& container, + const GenericPathContainer& distance_container, const NodeIndex expected_paths[], const PathDistance expected_distances[]) { for (typename GraphType::NodeIterator iterator(graph); iterator.Ok(); @@ -102,22 +104,21 @@ void CheckPathData(const GraphType& graph, const PathContainer& container, } template -void CheckPathDataFromGraph(const GraphType& graph, - const PathContainer& container, - const PathContainer& distance_container, - const NodeIndex expected_paths[], - const PathDistance expected_distances[]) { +void CheckPathDataFromGraph( + const GraphType& graph, const GenericPathContainer& container, + const GenericPathContainer& distance_container, + const NodeIndex expected_paths[], const PathDistance expected_distances[]) { for (typename GraphType::NodeIndex tail : graph.AllNodes()) { CheckPathDataRowFromGraph(graph, container, distance_container, expected_paths, expected_distances, tail); } } -#define BUILD_CONTAINERS() \ - PathContainer container; \ - PathContainer::BuildInMemoryCompactPathContainer(&container); \ - PathContainer distance_container; \ - PathContainer::BuildPathDistanceContainer(&distance_container) +#define BUILD_CONTAINERS() \ + auto container = \ + GenericPathContainer::BuildInMemoryCompactPathContainer(); \ + auto distance_container = \ + GenericPathContainer::BuildPathDistanceContainer() template void TestShortestPathsFromGraph(const GraphType& graph, @@ -254,8 +255,9 @@ class GraphShortestPathsDeathTest : public testing::Test {}; template class GraphShortestPathsTest : public testing::Test {}; -typedef testing::Types, StaticGraph<>, ReverseArcListGraph<>, - ReverseArcStaticGraph<>, ReverseArcMixedGraph<> > +typedef testing::Types< + ::util::ListGraph<>, ::util::StaticGraph<>, ::util::ReverseArcListGraph<>, + ::util::ReverseArcStaticGraph<>, ::util::ReverseArcMixedGraph<>> GraphTypesForShortestPathsTesting; TYPED_TEST_SUITE(GraphShortestPathsDeathTest, @@ -274,7 +276,7 @@ TYPED_TEST(GraphShortestPathsDeathTest, ShortestPathsEmptyGraph) { // Test on a disconnected graph (set of nodes pointing to themselves). TYPED_TEST(GraphShortestPathsDeathTest, ShortestPathsAllDisconnected) { - const typename TypeParam::NodeIndex kUnconnected = -1; + const typename TypeParam::NodeIndex kUnconnected = TypeParam::kNilNode; const int kNodes = 3; const typename TypeParam::NodeIndex kArcs[][2] = {{0, 0}, {1, 1}, {2, 2}}; const PathDistance kArcLengths[] = {0, 0, 0}; @@ -349,8 +351,8 @@ TYPED_TEST(GraphShortestPathsDeathTest, MismatchedData) { graph.AddArc(0, 1); graph.AddArc(1, 0); std::vector lengths = {0}; - PathContainer container; - PathContainer::BuildInMemoryCompactPathContainer(&container); + auto container = + GenericPathContainer::BuildInMemoryCompactPathContainer(); EXPECT_DEATH(ComputeAllToAllShortestPathsWithMultipleThreads(graph, lengths, 1, &container), "Number of arcs in graph must match arc length vector size"); @@ -358,10 +360,13 @@ TYPED_TEST(GraphShortestPathsDeathTest, MismatchedData) { // Test the case where some sources are not strongly connected to themselves. TYPED_TEST(GraphShortestPathsDeathTest, SourceNotConnectedToItself) { + const typename TypeParam::NodeIndex kUnconnected = TypeParam::kNilNode; const int kNodes = 3; const typename TypeParam::NodeIndex kArcs[][2] = {{1, 2}, {2, 2}}; const PathDistance kArcLengths[] = {1, 0}; - const int kExpectedPaths[] = {-1, -1, -1, -1, -1, 1, -1, -1, 2}; + const int kExpectedPaths[] = {kUnconnected, kUnconnected, kUnconnected, + kUnconnected, kUnconnected, 1, + kUnconnected, kUnconnected, 2}; const PathDistance kExpectedDistances[] = {kDisconnectedPathDistance, kDisconnectedPathDistance, kDisconnectedPathDistance, @@ -414,10 +419,10 @@ TYPED_TEST(GraphShortestPathsTest, DISABLED_LargeRandomShortestPaths) { lengths.push_back(length); } } - typename TypeParam::NodeIndex prev_index = -1; - typename TypeParam::NodeIndex first_index = -1; + typename TypeParam::NodeIndex prev_index = TypeParam::kNilNode; + typename TypeParam::NodeIndex first_index = TypeParam::kNilNode; for (const typename TypeParam::NodeIndex node_index : graph.AllNodes()) { - if (prev_index != -1) { + if (prev_index != TypeParam::kNilNode) { graph.AddArc(prev_index, node_index); lengths.push_back(kConnectionArcLength); } else { @@ -430,7 +435,7 @@ TYPED_TEST(GraphShortestPathsTest, DISABLED_LargeRandomShortestPaths) { std::vector permutation; graph.Build(&permutation); util::Permute(permutation, &lengths); - std::vector > components; + std::vector> components; ::FindStronglyConnectedComponents(graph.num_nodes(), graph, &components); CHECK_EQ(1, components.size()); CHECK_EQ(kSize, components[0].size()); @@ -441,18 +446,18 @@ TYPED_TEST(GraphShortestPathsTest, DISABLED_LargeRandomShortestPaths) { sources[i] = absl::Uniform(randomizer, 0, graph.num_nodes()); } const int kThreads = 10; - PathContainer container; - PathContainer::BuildInMemoryCompactPathContainer(&container); + auto container = + GenericPathContainer::BuildInMemoryCompactPathContainer(); ComputeManyToManyShortestPathsWithMultipleThreads( graph, lengths, sources, sources, kThreads, &container); - PathContainer distance_container; - PathContainer::BuildPathDistanceContainer(&distance_container); + auto distance_container = + GenericPathContainer::BuildPathDistanceContainer(); ComputeManyToManyShortestPathsWithMultipleThreads( graph, lengths, sources, sources, kThreads, &distance_container); for (int tail = 0; tail < sources.size(); ++tail) { for (int head = 0; head < sources.size(); ++head) { - EXPECT_NE( - -1, container.GetPenultimateNodeInPath(sources[tail], sources[head])); + EXPECT_NE(TypeParam::kNilNode, container.GetPenultimateNodeInPath( + sources[tail], sources[head])); EXPECT_NE(kDisconnectedPathDistance, container.GetDistance(sources[tail], sources[head])); EXPECT_NE(kDisconnectedPathDistance, diff --git a/ortools/routing/routing.cc b/ortools/routing/routing.cc index afddeccdd2b..cb2d1419da2 100644 --- a/ortools/routing/routing.cc +++ b/ortools/routing/routing.cc @@ -63,7 +63,7 @@ #include "ortools/constraint_solver/constraint_solveri.h" #include "ortools/constraint_solver/solver_parameters.pb.h" #include "ortools/graph/connected_components.h" -#include "ortools/graph/ebert_graph.h" +#include "ortools/graph/graph.h" #include "ortools/graph/linear_assignment.h" #include "ortools/routing/constraints.h" #include "ortools/routing/decision_builders.h" @@ -89,6 +89,13 @@ #include "ortools/util/sorted_interval_list.h" #include "ortools/util/stats.h" +namespace { +using GraphNodeIndex = int32_t; +using GraphArcIndex = int32_t; +using Graph = ::util::ListGraph; +using CostValue = int64_t; +} // namespace + namespace operations_research { class Cross; class Exchange; @@ -3573,8 +3580,8 @@ int64_t RoutingModel::ComputeLowerBound() { return 0; } const int num_nodes = Size() + vehicles_; - ForwardStarGraph graph(2 * num_nodes, num_nodes * num_nodes); - LinearSumAssignment linear_sum_assignment(graph, num_nodes); + Graph graph(2 * num_nodes, num_nodes * num_nodes); + LinearSumAssignment linear_sum_assignment(graph, num_nodes); // Adding arcs for non-end nodes, based on possible values of next variables. // Left nodes in the bipartite are indexed from 0 to num_nodes - 1; right // nodes are indexed from num_nodes to 2 * num_nodes - 1. @@ -3590,8 +3597,8 @@ int64_t RoutingModel::ComputeLowerBound() { } // The index of a right node in the bipartite graph is the index // of the successor offset by the number of nodes. - const ArcIndex arc = graph.AddArc(tail, num_nodes + head); - const CostValue cost = GetHomogeneousCost(tail, head); + const GraphArcIndex arc = graph.AddArc(tail, num_nodes + head); + const ::CostValue cost = GetHomogeneousCost(tail, head); linear_sum_assignment.SetArcCost(arc, cost); } } @@ -3599,7 +3606,8 @@ int64_t RoutingModel::ComputeLowerBound() { // Therefore we are creating fake assignments for end nodes, forced to point // to the equivalent start node with a cost of 0. for (int tail = Size(); tail < num_nodes; ++tail) { - const ArcIndex arc = graph.AddArc(tail, num_nodes + Start(tail - Size())); + const GraphArcIndex arc = + graph.AddArc(tail, num_nodes + Start(tail - Size())); linear_sum_assignment.SetArcCost(arc, 0); } if (linear_sum_assignment.ComputeAssignment()) {