Merge branch 'main' of github.com:google/or-tools

modified:   ortools/algorithms/BUILD.bazel
	new file:   ortools/algorithms/set_cover.proto
	modified:   ortools/algorithms/set_cover_ledger.cc
	modified:   ortools/algorithms/set_cover_ledger.h
	modified:   ortools/algorithms/set_cover_model.cc
	modified:   ortools/algorithms/set_cover_model.h
	modified:   ortools/algorithms/set_cover_test.cc
This commit is contained in:
Laurent Perron
2023-12-07 19:19:46 +01:00
7 changed files with 231 additions and 41 deletions

View File

@@ -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",
],

View 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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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