From 71ff62b3c5ac3d1860ebe128aaacf6181b77a29e Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Wed, 6 Dec 2023 18:20:29 +0100 Subject: [PATCH] algorithms: export from google3 --- ortools/algorithms/BUILD.bazel | 14 ++++ ortools/algorithms/set_cover.proto | 67 +++++++++++++++ ortools/algorithms/set_cover_ledger.cc | 27 +++++++ ortools/algorithms/set_cover_ledger.h | 7 ++ ortools/algorithms/set_cover_model.cc | 37 +++++++++ ortools/algorithms/set_cover_model.h | 12 ++- ortools/algorithms/set_cover_test.cc | 108 ++++++++++++++++--------- 7 files changed, 231 insertions(+), 41 deletions(-) create mode 100644 ortools/algorithms/set_cover.proto diff --git a/ortools/algorithms/BUILD.bazel b/ortools/algorithms/BUILD.bazel index 55cf634545..59fde0f2e6 100644 --- a/ortools/algorithms/BUILD.bazel +++ b/ortools/algorithms/BUILD.bazel @@ -99,11 +99,25 @@ cc_library( ) # Weighted set covering +proto_library( + name = "set_cover_proto", + srcs = ["set_cover.proto"], + #deps = [ + # "//storage/util/int128:int128_proto", + #], +) + +cc_proto_library( + name = "set_cover_cc_proto", + deps = [":set_cover_proto"], +) + cc_library( name = "set_cover_model", srcs = ["set_cover_model.cc"], hdrs = ["set_cover_model.h"], deps = [ + ":set_cover_cc_proto", "//ortools/lp_data:base", "@com_google_absl//absl/log:check", ], diff --git a/ortools/algorithms/set_cover.proto b/ortools/algorithms/set_cover.proto new file mode 100644 index 0000000000..b04366098a --- /dev/null +++ b/ortools/algorithms/set_cover.proto @@ -0,0 +1,67 @@ +// Copyright 2010-2022 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. + +syntax = "proto3"; + +package operations_research; + +option java_package = "com.google.ortools.algorithms"; +option java_multiple_files = true; + +message SetCoverProto { + message Subset { + // The cost for using the given subset. + optional double cost = 1; + + // The list of elements in the subset. + repeated int32 element = 2 [packed = true]; + } + + // The list of subsets in the model. + repeated Subset subset = 1; + + // A user-defined name for the model. + optional string name = 2; +} + +message SetCoverSolutionResponse { + // Result of the optimization. + enum Status { + // Undefined. + UNDEFINED = 0; + // The solver found the proven optimal solution. + OPTIMAL = 1; + // The solver had enough time to find some solution that satisfied all + // constraints, but it did not reach the optimal. + FEASIBLE = 2; + // The model does not have any solution. + INFEASIBLE = 3; + // The model is invalid. + INVALID = 4; + } + // For future use. TODO(user): Implement. + optional Status status = 1; + + // The number of subsets that are selected in the solution. This is used + // to decompress their indices below. + optional int32 num_subsets = 2; + + // The list of the subsets selected in the solution. + repeated int32 subset = 3 [packed = true]; + + // The cost of the solution, as computed by the algorithm. + optional double cost = 4; + + // A lower bound of the solution, as computed by the algorithm. + optional double cost_lower_bound = 5; +} diff --git a/ortools/algorithms/set_cover_ledger.cc b/ortools/algorithms/set_cover_ledger.cc index 20397af44e..41c38ccd89 100644 --- a/ortools/algorithms/set_cover_ledger.cc +++ b/ortools/algorithms/set_cover_ledger.cc @@ -14,6 +14,7 @@ #include "ortools/algorithms/set_cover_ledger.h" #include +#include #include #include "absl/container/flat_hash_set.h" @@ -374,4 +375,30 @@ std::vector SetCoverLedger::ComputeResettableSubsets() const { return focus; } +SetCoverSolutionResponse SetCoverLedger::ExportSolutionAsProto() const { + SetCoverSolutionResponse message; + message.set_num_subsets(is_selected_.size().value()); + Cost lower_bound = std::numeric_limits::max(); + for (SubsetIndex subset(0); subset < model_->num_subsets(); ++subset) { + if (is_selected_[subset]) { + message.add_subset(subset.value()); + } + lower_bound = std::min(model_->subset_costs()[subset], lower_bound); + } + message.set_cost(cost_); + message.set_cost_lower_bound(lower_bound); + return message; +} + +void SetCoverLedger::ImportSolutionFromProto( + const SetCoverSolutionResponse& message) { + is_selected_.resize(SubsetIndex(message.num_subsets()), false); + for (auto s : message.subset()) { + is_selected_[SubsetIndex(s)] = true; + } + MakeDataConsistent(); + Cost cost = message.cost(); + CHECK_EQ(cost, cost_); +} + } // namespace operations_research diff --git a/ortools/algorithms/set_cover_ledger.h b/ortools/algorithms/set_cover_ledger.h index f582217f58..b73b120ed9 100644 --- a/ortools/algorithms/set_cover_ledger.h +++ b/ortools/algorithms/set_cover_ledger.h @@ -18,6 +18,7 @@ #include +#include "ortools/algorithms/set_cover.pb.h" #include "ortools/algorithms/set_cover_model.h" namespace operations_research { @@ -139,6 +140,12 @@ class SetCoverLedger { std::vector ComputeResettableSubsets() const; + // Returns the current solution as a proto. + SetCoverSolutionResponse ExportSolutionAsProto() const; + + // Imports the solution from a proto. + void ImportSolutionFromProto(const SetCoverSolutionResponse& message); + private: // Recomputes the cost from scratch from c. Cost ComputeCost(const SubsetBoolVector& c) const; diff --git a/ortools/algorithms/set_cover_model.cc b/ortools/algorithms/set_cover_model.cc index a4e45122ce..58c2e21792 100644 --- a/ortools/algorithms/set_cover_model.cc +++ b/ortools/algorithms/set_cover_model.cc @@ -14,8 +14,10 @@ #include "ortools/algorithms/set_cover_model.h" #include +#include #include "absl/log/check.h" +#include "ortools/algorithms/set_cover.pb.h" #include "ortools/base/logging.h" #include "ortools/lp_data/lp_types.h" // For StrictITIVector. @@ -53,6 +55,7 @@ void SetCoverModel::AddElementToLastSubset(int element) { } void SetCoverModel::SetSubsetCost(int subset, Cost cost) { + CHECK(std::isfinite(cost)); DCHECK_GE(subset, 0); const SubsetIndex subset_index(subset); const SubsetIndex num_subsets = columns_.size(); @@ -144,4 +147,38 @@ bool SetCoverModel::ComputeFeasibility() const { return true; } +SetCoverProto SetCoverModel::ExportModelAsProto() { + SetCoverProto message; + for (SubsetIndex subset(0); subset < columns_.size(); ++subset) { + SetCoverProto::Subset* subset_proto = message.add_subset(); + subset_proto->set_cost(subset_costs_[subset]); + std::sort(columns_[subset].begin(), columns_[subset].end()); + for (const ElementIndex element : columns_[subset]) { + subset_proto->add_element(element.value()); + } + } + return message; +} + +void SetCoverModel::ImportModelFromProto(const SetCoverProto& message) { + columns_.clear(); + subset_costs_.clear(); + ReserveNumSubsets(message.subset_size()); + SubsetIndex subset_index(0); + for (const SetCoverProto::Subset& subset_proto : message.subset()) { + subset_costs_[SubsetIndex(subset_index)] = subset_proto.cost(); + if (subset_proto.element_size() > 0) { + columns_[subset_index].reserve(EntryIndex(subset_proto.element_size())); + for (auto element : subset_proto.element()) { + columns_[subset_index].push_back(ElementIndex(element)); + num_elements_ = + ElementIndex(std::max(num_elements_.value(), element + 1)); + } + ++subset_index; + } + } + UpdateAllSubsetsList(); + CreateSparseRowView(); +} + } // namespace operations_research diff --git a/ortools/algorithms/set_cover_model.h b/ortools/algorithms/set_cover_model.h index e246980161..a5c9186fcf 100644 --- a/ortools/algorithms/set_cover_model.h +++ b/ortools/algorithms/set_cover_model.h @@ -17,6 +17,7 @@ #include #include "absl/log/check.h" +#include "ortools/algorithms/set_cover.pb.h" #include "ortools/lp_data/lp_types.h" // For StrictITIVector. // Representation class for the weighted set-covering problem. @@ -89,8 +90,6 @@ class SetCoverModel { const SubsetCostVector& subset_costs() const { return subset_costs_; } - Cost subset_costs(SubsetIndex subset) const { return subset_costs_[subset]; } - const SparseColumnView& columns() const { return columns_; } const SparseColumn& columns(SubsetIndex subset) const { @@ -123,6 +122,7 @@ class SetCoverModel { void AddElementToLastSubset(ElementIndex element); // Sets 'cost' to an already existing 'subset'. + // This will CHECK-fail if cost is infinite or a NaN. void SetSubsetCost(int subset, Cost cost); // Adds 'element' to and already existing 'subset'. @@ -141,6 +141,14 @@ class SetCoverModel { // Reserves num_elements rows in the column indexed by subset. void ReserveNumElementsInSubset(int num_elements, int subset); + // Returns the model as a SetCoverProto. The function is not const because + // the element indices in the columns need to be sorted for the representation + // as a protobuf to be canonical. + SetCoverProto ExportModelAsProto(); + + // Imports the model from a SetCoverProto. + void ImportModelFromProto(const SetCoverProto& message); + private: // Updates the all_subsets_ vector so that it always contains 0 to // columns.size() - 1 diff --git a/ortools/algorithms/set_cover_test.cc b/ortools/algorithms/set_cover_test.cc index 0fd2b28aaf..d1a4b5d51b 100644 --- a/ortools/algorithms/set_cover_test.cc +++ b/ortools/algorithms/set_cover_test.cc @@ -19,7 +19,9 @@ #include "absl/log/check.h" #include "absl/strings/str_cat.h" #include "benchmark/benchmark.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "ortools/algorithms/set_cover.pb.h" #include "ortools/algorithms/set_cover_ledger.h" #include "ortools/algorithms/set_cover_mip.h" #include "ortools/algorithms/set_cover_model.h" @@ -28,45 +30,6 @@ namespace operations_research { namespace { -TEST(SetCoverTest, InitialValues) { - SetCoverModel model; - model.AddEmptySubset(1); - model.AddElementToLastSubset(0); - model.AddEmptySubset(1); - model.AddElementToLastSubset(1); - model.AddElementToLastSubset(2); - model.AddEmptySubset(1); - model.AddElementToLastSubset(1); - model.AddEmptySubset(1); - model.AddElementToLastSubset(2); - EXPECT_TRUE(model.ComputeFeasibility()); - - SetCoverLedger ledger(&model); - TrivialSolutionGenerator trivial(&ledger); - CHECK(trivial.NextSolution()); - LOG(INFO) << "TrivialSolutionGenerator cost: " << ledger.cost(); - EXPECT_TRUE(ledger.CheckSolution()); - - GreedySolutionGenerator greedy(&ledger); - CHECK(greedy.NextSolution()); - LOG(INFO) << "GreedySolutionGenerator cost: " << ledger.cost(); - EXPECT_TRUE(ledger.CheckSolution()); - - SteepestSearch steepest(&ledger); - CHECK(steepest.NextSolution(500)); - LOG(INFO) << "SteepestSearch cost: " << ledger.cost(); - EXPECT_TRUE(ledger.CheckSolution()); -} - -TEST(SetCoverTest, Infeasible) { - SetCoverModel model; - model.AddEmptySubset(1); - model.AddElementToLastSubset(0); - model.AddEmptySubset(1); - model.AddElementToLastSubset(3); - EXPECT_FALSE(model.ComputeFeasibility()); -} - SetCoverModel CreateKnightsCoverModel(int num_rows, int num_cols) { SetCoverModel model; constexpr int knight_row_move[] = {2, 1, -1, -2, -2, -1, 1, 2}; @@ -107,6 +70,73 @@ void DisplayKnightsCoverSolution(const SubsetBoolVector& choices, int num_rows, } } +TEST(SetCoverProtoTest, SaveReload) { + SetCoverModel model = CreateKnightsCoverModel(10, 10); + SetCoverProto proto = model.ExportModelAsProto(); + SetCoverModel reloaded; + reloaded.ImportModelFromProto(proto); + EXPECT_EQ(model.num_subsets(), reloaded.num_subsets()); + EXPECT_EQ(model.num_elements(), reloaded.num_elements()); + EXPECT_EQ(model.subset_costs(), reloaded.subset_costs()); + EXPECT_EQ(model.columns(), reloaded.columns()); +} + +TEST(SolutionProtoTest, SaveReloadTwice) { + SetCoverModel model = CreateKnightsCoverModel(10, 10); + SetCoverLedger ledger(&model); + GreedySolutionGenerator greedy(&ledger); + CHECK(greedy.NextSolution()); + EXPECT_TRUE(ledger.CheckSolution()); + SetCoverSolutionResponse greedy_proto = ledger.ExportSolutionAsProto(); + SteepestSearch steepest(&ledger); + CHECK(steepest.NextSolution(500)); + EXPECT_TRUE(ledger.CheckSolution()); + SetCoverSolutionResponse steepest_proto = ledger.ExportSolutionAsProto(); + ledger.ImportSolutionFromProto(greedy_proto); + CHECK(steepest.NextSolution(500)); + EXPECT_TRUE(ledger.CheckSolution()); + SetCoverSolutionResponse reloaded_proto = ledger.ExportSolutionAsProto(); +} + +TEST(SetCoverTest, InitialValues) { + SetCoverModel model; + model.AddEmptySubset(1); + model.AddElementToLastSubset(0); + model.AddEmptySubset(1); + model.AddElementToLastSubset(1); + model.AddElementToLastSubset(2); + model.AddEmptySubset(1); + model.AddElementToLastSubset(1); + model.AddEmptySubset(1); + model.AddElementToLastSubset(2); + EXPECT_TRUE(model.ComputeFeasibility()); + + SetCoverLedger ledger(&model); + TrivialSolutionGenerator trivial(&ledger); + CHECK(trivial.NextSolution()); + LOG(INFO) << "TrivialSolutionGenerator cost: " << ledger.cost(); + EXPECT_TRUE(ledger.CheckSolution()); + + GreedySolutionGenerator greedy(&ledger); + CHECK(greedy.NextSolution()); + LOG(INFO) << "GreedySolutionGenerator cost: " << ledger.cost(); + EXPECT_TRUE(ledger.CheckSolution()); + + SteepestSearch steepest(&ledger); + CHECK(steepest.NextSolution(500)); + LOG(INFO) << "SteepestSearch cost: " << ledger.cost(); + EXPECT_TRUE(ledger.CheckSolution()); +} + +TEST(SetCoverTest, Infeasible) { + SetCoverModel model; + model.AddEmptySubset(1); + model.AddElementToLastSubset(0); + model.AddEmptySubset(1); + model.AddElementToLastSubset(3); + EXPECT_FALSE(model.ComputeFeasibility()); +} + #ifdef NDEBUG static constexpr int SIZE = 512; #else