algorithms: export from google3
This commit is contained in:
@@ -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",
|
||||
],
|
||||
|
||||
67
ortools/algorithms/set_cover.proto
Normal file
67
ortools/algorithms/set_cover.proto
Normal file
@@ -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;
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "ortools/algorithms/set_cover_ledger.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
@@ -374,4 +375,30 @@ std::vector<SubsetIndex> 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<Cost>::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
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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<SubsetIndex> 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;
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#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
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <vector>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user