bump set cover code

This commit is contained in:
Laurent Perron
2025-03-02 15:13:25 +01:00
parent f244e28cbf
commit b865d32eed
25 changed files with 217 additions and 295 deletions

View File

@@ -39,11 +39,21 @@ py_proto_library(
deps = [":set_cover_proto"],
)
cc_library(
name = "base_types",
hdrs = ["base_types.h"],
deps = [
"//ortools/base:intops",
"//ortools/base:strong_vector",
],
)
cc_library(
name = "set_cover_lagrangian",
srcs = ["set_cover_lagrangian.cc"],
hdrs = ["set_cover_lagrangian.h"],
deps = [
":base_types",
":set_cover_invariant",
":set_cover_model",
"//ortools/algorithms:adjustable_k_ary_heap",
@@ -58,6 +68,7 @@ cc_library(
srcs = ["set_cover_model.cc"],
hdrs = ["set_cover_model.h"],
deps = [
":base_types",
":set_cover_cc_proto",
"//ortools/algorithms:radix_sort",
"//ortools/base:intops",
@@ -78,6 +89,7 @@ cc_library(
srcs = ["set_cover_invariant.cc"],
hdrs = ["set_cover_invariant.h"],
deps = [
":base_types",
":set_cover_cc_proto",
":set_cover_model",
"//ortools/base",
@@ -93,6 +105,7 @@ cc_library(
srcs = ["set_cover_heuristics.cc"],
hdrs = ["set_cover_heuristics.h"],
deps = [
":base_types",
":set_cover_invariant",
":set_cover_model",
"//ortools/algorithms:adjustable_k_ary_heap",
@@ -111,6 +124,7 @@ cc_library(
srcs = ["set_cover_mip.cc"],
hdrs = ["set_cover_mip.h"],
deps = [
":base_types",
":set_cover_invariant",
":set_cover_model",
"//ortools/linear_solver",
@@ -126,6 +140,7 @@ cc_library(
srcs = ["set_cover_reader.cc"],
hdrs = ["set_cover_reader.h"],
deps = [
":base_types",
":set_cover_cc_proto",
":set_cover_model",
"//ortools/base:file",
@@ -143,6 +158,7 @@ cc_library(
srcs = ["assignment.cc"],
hdrs = ["assignment.h"],
deps = [
":base_types",
":capacity_invariant",
":set_cover_invariant",
":set_cover_model",
@@ -168,6 +184,7 @@ cc_binary(
name = "set_cover_solve",
srcs = ["set_cover_solve.cc"],
deps = [
":base_types",
":set_cover_heuristics",
":set_cover_invariant",
":set_cover_model",
@@ -188,6 +205,7 @@ cc_test(
timeout = "eternal",
srcs = ["set_cover_test.cc"],
deps = [
":base_types",
":set_cover_cc_proto",
":set_cover_heuristics",
":set_cover_invariant",
@@ -224,6 +242,7 @@ cc_library(
srcs = ["capacity_model.cc"],
hdrs = ["capacity_model.h"],
deps = [
":base_types",
":capacity_cc_proto",
":set_cover_model",
"//ortools/base:intops",
@@ -238,7 +257,6 @@ cc_test(
srcs = ["capacity_model_test.cc"],
deps = [
":capacity_model",
":set_cover_model",
"//ortools/base:gmock_main",
],
)
@@ -248,8 +266,10 @@ cc_library(
srcs = ["capacity_invariant.cc"],
hdrs = ["capacity_invariant.h"],
deps = [
":base_types",
":capacity_model",
":set_cover_model",
"//ortools/util:saturated_arithmetic",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
],

View File

@@ -16,6 +16,7 @@
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "ortools/base/mathutil.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/capacity_invariant.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"

View File

@@ -16,6 +16,7 @@
#include <vector>
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/capacity_invariant.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"

View File

@@ -0,0 +1,70 @@
// Copyright 2010-2025 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.
#ifndef OR_TOOLS_SET_COVER_BASE_TYPES_H_
#define OR_TOOLS_SET_COVER_BASE_TYPES_H_
#include <cstdint>
#include "ortools/base/strong_int.h"
#include "ortools/base/strong_vector.h"
namespace operations_research {
// Basic non-strict type for cost. The speed penalty for using double is ~2%.
using Cost = double;
// Base non-strict integer type for counting elements and subsets.
// Using ints makes it possible to represent problems with more than 2 billion
// (2e9) elements and subsets. If need arises one day, BaseInt can be split
// into SubsetBaseInt and ElementBaseInt.
// Quick testing has shown a slowdown of about 20-25% when using int64_t.
using BaseInt = int32_t;
// We make heavy use of strong typing to avoid obvious mistakes.
// Subset index.
DEFINE_STRONG_INT_TYPE(SubsetIndex, BaseInt);
// Element index.
DEFINE_STRONG_INT_TYPE(ElementIndex, BaseInt);
// Position in a vector. The vector may either represent a column, i.e. a
// subset with all its elements, or a row, i,e. the list of subsets which
// contain a given element.
DEFINE_STRONG_INT_TYPE(ColumnEntryIndex, BaseInt);
DEFINE_STRONG_INT_TYPE(RowEntryIndex, BaseInt);
using SubsetRange = util_intops::StrongIntRange<SubsetIndex>;
using ElementRange = util_intops::StrongIntRange<ElementIndex>;
using ColumnEntryRange = util_intops::StrongIntRange<ColumnEntryIndex>;
using SubsetCostVector = util_intops::StrongVector<SubsetIndex, Cost>;
using ElementCostVector = util_intops::StrongVector<ElementIndex, Cost>;
using SparseColumn = util_intops::StrongVector<ColumnEntryIndex, ElementIndex>;
using SparseRow = util_intops::StrongVector<RowEntryIndex, SubsetIndex>;
using ElementToIntVector = util_intops::StrongVector<ElementIndex, BaseInt>;
using SubsetToIntVector = util_intops::StrongVector<SubsetIndex, BaseInt>;
// Views of the sparse vectors. These need not be aligned as it's their contents
// that need to be aligned.
using SparseColumnView = util_intops::StrongVector<SubsetIndex, SparseColumn>;
using SparseRowView = util_intops::StrongVector<ElementIndex, SparseRow>;
using SubsetBoolVector = util_intops::StrongVector<SubsetIndex, bool>;
using ElementBoolVector = util_intops::StrongVector<ElementIndex, bool>;
} // namespace operations_research
#endif // OR_TOOLS_SET_COVER_BASE_TYPES_H_

View File

@@ -43,15 +43,15 @@ message CapacityConstraintProto {
message CapacityTerm {
// The subset this weight corresponds to (index of the subset in the
// `subset` repeated field in `SetCoverProto`).
int32 subset = 1;
int64 subset = 1;
message ElementWeightPair {
// The element this weight corresponds to (value of `element` in
// `SetCoverProto.Subset`).
int32 element = 1;
int64 element = 1;
// The weight of the element.
double weight = 2;
int64 weight = 2;
}
repeated ElementWeightPair element_weights = 2;
@@ -67,9 +67,9 @@ message CapacityConstraintProto {
// The minimum amount of resource that must be consumed. At least one of
// `min_capacity` and `max_capacity` must be present.
double min_capacity = 2;
int64 min_capacity = 2;
// The maximum amount of resource that can be consumed. At least one of
// `min_capacity` and `max_capacity` must be present.
double max_capacity = 3;
int64 max_capacity = 3;
}

View File

@@ -13,10 +13,14 @@
#include "ortools/set_cover/capacity_invariant.h"
#include <limits>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/capacity_model.h"
#include "ortools/set_cover/set_cover_model.h"
#include "ortools/util/saturated_arithmetic.h"
namespace operations_research {
@@ -25,48 +29,55 @@ void CapacityInvariant::Clear() {
is_selected_.assign(set_cover_model_->num_subsets(), false);
}
double CapacityInvariant::ComputeSlackChange(const SubsetIndex subset) const {
double slack_change = 0.0;
namespace {
// Returns true if the addition of `q` to `sum` overflows, for q >= 0.
bool PositiveAddOverflows(CapacityWeight sum, CapacityWeight q) {
DCHECK_GE(q, 0);
return sum > std::numeric_limits<CapacityWeight>::max() - q;
}
// Returns true if the addition of `q` to `sum` overflows, for q <= 0.
bool NegativeAddOverflows(CapacityWeight sum, CapacityWeight q) {
DCHECK_LE(q, 0);
return sum < std::numeric_limits<CapacityWeight>::min() - q;
}
} // namespace
CapacityWeight CapacityInvariant::ComputeSlackChange(
const SubsetIndex subset) const {
CapacityWeight slack_change = 0;
for (CapacityTermIndex term : model_->TermRange()) {
if (model_->GetTermSubsetIndex(term) == subset) {
// Hypothesis: GetTermSubsetIndex(term) is an element of the subset.
// This information is stored in a SetCoverModel instance.
slack_change += model_->GetTermCapacityWeight(term);
const CapacityWeight term_weight = model_->GetTermCapacityWeight(term);
// Make sure that the slack change will not overflow.
CHECK(!PositiveAddOverflows(slack_change, term_weight));
slack_change += term_weight;
}
}
return slack_change;
}
bool CapacityInvariant::SlackChangeFitsConstraint(
const double slack_change) const {
const double new_slack = current_slack_ + slack_change;
CapacityWeight slack_change) const {
CHECK(!AddOverflows(current_slack_, slack_change));
const CapacityWeight new_slack = current_slack_ + slack_change;
return new_slack >= model_->GetMinimumCapacity() &&
new_slack <= model_->GetMaximumCapacity();
}
bool CapacityInvariant::Flip(SubsetIndex subset) {
DCHECK_LT(subset.value(), set_cover_model_->num_subsets())
<< "Invalid subset: " << subset;
return !is_selected_[subset] ? Select(subset) : Deselect(subset);
}
bool CapacityInvariant::CanFlip(SubsetIndex subset) const {
DCHECK_LT(subset.value(), set_cover_model_->num_subsets())
<< "Invalid subset: " << subset;
return !is_selected_[subset] ? CanSelect(subset) : CanDeselect(subset);
}
bool CapacityInvariant::Select(SubsetIndex subset) {
DVLOG(1) << "[Capacity constraint] Selecting subset " << subset;
DCHECK(!is_selected_[subset]);
const double slack_change = ComputeSlackChange(subset);
const CapacityWeight slack_change = ComputeSlackChange(subset);
if (!SlackChangeFitsConstraint(slack_change)) {
DVLOG(1) << "[Capacity constraint] Selecting subset " << subset
<< ": infeasible";
return false;
}
CHECK(!PositiveAddOverflows(current_slack_, slack_change));
is_selected_[subset] = true;
current_slack_ += slack_change;
DVLOG(1) << "[Capacity constraint] New slack: " << current_slack_;
@@ -77,7 +88,7 @@ bool CapacityInvariant::CanSelect(SubsetIndex subset) const {
DVLOG(1) << "[Capacity constraint] Can select subset " << subset << "?";
DCHECK(!is_selected_[subset]);
const double slack_change = ComputeSlackChange(subset);
const CapacityWeight slack_change = ComputeSlackChange(subset);
DVLOG(1) << "[Capacity constraint] New slack if selecting: "
<< current_slack_ + slack_change;
return SlackChangeFitsConstraint(slack_change);
@@ -87,13 +98,13 @@ bool CapacityInvariant::Deselect(SubsetIndex subset) {
DVLOG(1) << "[Capacity constraint] Deselecting subset " << subset;
DCHECK(is_selected_[subset]);
const double slack_change = -ComputeSlackChange(subset);
const CapacityWeight slack_change = -ComputeSlackChange(subset);
if (!SlackChangeFitsConstraint(slack_change)) {
DVLOG(1) << "[Capacity constraint] Deselecting subset " << subset
<< ": infeasible";
return false;
}
CHECK(!NegativeAddOverflows(current_slack_, slack_change));
is_selected_[subset] = false;
current_slack_ += slack_change;
DVLOG(1) << "[Capacity constraint] New slack: " << current_slack_;
@@ -104,7 +115,7 @@ bool CapacityInvariant::CanDeselect(SubsetIndex subset) const {
DVLOG(1) << "[Capacity constraint] Can deselect subset " << subset << "?";
DCHECK(is_selected_[subset]);
const double slack_change = -ComputeSlackChange(subset);
const CapacityWeight slack_change = -ComputeSlackChange(subset);
DVLOG(1) << "[Capacity constraint] New slack if deselecting: "
<< current_slack_ + slack_change;
return SlackChangeFitsConstraint(slack_change);

View File

@@ -15,6 +15,7 @@
#define OR_TOOLS_SET_COVER_CAPACITY_INVARIANT_H_
#include "absl/log/check.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/capacity_model.h"
#include "ortools/set_cover/set_cover_model.h"
@@ -32,18 +33,6 @@ class CapacityInvariant {
// Clears the invariant.
void Clear();
// Returns `true` when the constraint is not violated by this flipping move
// and incrementally updates the invariant. Otherwise, returns `false` and
// does not change the object.
//
// Flips is_selected_[subset] to its negation, by calling Select or Deselect
// depending on value.
bool Flip(SubsetIndex subset);
// Returns `true` when the constraint would not be violated if this flipping
// move is performed. Otherwise, returns `false`. The object never changes.
bool CanFlip(SubsetIndex subset) const;
// Returns `true` when the constraint is not violated by selecting all of the
// items in the subset and incrementally updates the invariant. Otherwise,
// returns `false` and does not change the object. (If the subset is already
@@ -87,7 +76,7 @@ class CapacityInvariant {
SetCoverModel* set_cover_model_;
// Current slack of the constraint.
operations_research::CapacityWeight current_slack_;
CapacityWeight current_slack_;
// Current solution assignment.
// TODO(user): reuse the assignment of a SetCoverInvariant.
@@ -96,11 +85,11 @@ class CapacityInvariant {
// Determines the change in slack when (de)selecting the given subset.
// The returned value is nonnegative; add it to the slack when selecting
// and subtract it when deselecting.
double ComputeSlackChange(SubsetIndex subset) const;
CapacityWeight ComputeSlackChange(SubsetIndex subset) const;
// Determines whether the given slack change violates the constraint
// (`false`) or not (`true`).
bool SlackChangeFitsConstraint(double slack_change) const;
bool SlackChangeFitsConstraint(CapacityWeight slack_change) const;
};
} // namespace operations_research

View File

@@ -34,21 +34,21 @@ TEST(CapacityModel, ChecksConstraintViolation) {
CapacityInvariant cinv(&m, &sc);
// Current assignment: [false, false]. Current activation: 0.
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(0))); // All moves are possible.
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(1)));
EXPECT_TRUE(cinv.CanSelect(SubsetIndex(0))); // All moves are possible.
EXPECT_TRUE(cinv.CanSelect(SubsetIndex(1)));
EXPECT_TRUE(cinv.Flip(SubsetIndex(0))); // Select returns true.
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(0))); // Undoing: still valid.
EXPECT_FALSE(cinv.CanFlip(SubsetIndex(1))); // Impossible move.
EXPECT_FALSE(cinv.Flip(SubsetIndex(1))); // Select returns false.
EXPECT_TRUE(cinv.Select(SubsetIndex(0)));
EXPECT_TRUE(cinv.CanDeselect(SubsetIndex(0))); // Undoing: still valid.
EXPECT_FALSE(cinv.CanSelect(SubsetIndex(1))); // Impossible move.
EXPECT_FALSE(cinv.Select(SubsetIndex(1)));
// Current assignment: [true, false]. Current activation: 1.
EXPECT_TRUE(cinv.Flip(SubsetIndex(0))); // Deselect returns true.
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(0))); // Undoing: still valid.
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(1))); // Valid when 0 not selected.
EXPECT_TRUE(cinv.Flip(SubsetIndex(1))); // Select returns true.
EXPECT_FALSE(cinv.CanFlip(SubsetIndex(0))); // Impossible move.
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(1))); // Undoing: still valid.
EXPECT_TRUE(cinv.Deselect(SubsetIndex(0)));
EXPECT_TRUE(cinv.CanSelect(SubsetIndex(0))); // Undoing: still valid.
EXPECT_TRUE(cinv.CanSelect(SubsetIndex(1))); // Valid when 0 not selected.
EXPECT_TRUE(cinv.Select(SubsetIndex(1)));
EXPECT_FALSE(cinv.CanSelect(SubsetIndex(0))); // Impossible move.
EXPECT_TRUE(cinv.CanDeselect(SubsetIndex(1))); // Undoing: still valid.
}
} // namespace

View File

@@ -14,20 +14,18 @@
#include "ortools/set_cover/capacity_model.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <numeric>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_model.h"
namespace operations_research {
void CapacityModel::AddTerm(SubsetIndex subset, ElementIndex element,
CapacityWeight weight) {
CHECK(std::isfinite(weight));
subsets_.push_back(subset);
elements_.push_back(element);
weights_.push_back(weight);
@@ -37,13 +35,11 @@ void CapacityModel::AddTerm(SubsetIndex subset, ElementIndex element,
}
void CapacityModel::SetMinimumCapacity(CapacityWeight min_capacity) {
CHECK(!std::isnan(min_capacity));
CHECK_NE(min_capacity, std::numeric_limits<CapacityWeight>::max());
min_capacity_ = min_capacity;
}
void CapacityModel::SetMaximumCapacity(CapacityWeight max_capacity) {
CHECK(!std::isnan(max_capacity));
CHECK_NE(max_capacity, std::numeric_limits<CapacityWeight>::min());
max_capacity_ = max_capacity;
}

View File

@@ -15,12 +15,14 @@
#define OR_TOOLS_SET_COVER_CAPACITY_MODEL_H_
#include <cmath>
#include <cstdint>
#include <limits>
#include <vector>
#include "absl/log/check.h"
#include "ortools/base/strong_int.h"
#include "ortools/base/strong_vector.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/capacity.pb.h"
#include "ortools/set_cover/set_cover_model.h"
@@ -36,7 +38,7 @@
namespace operations_research {
// Basic type for weights. For now, the same as `Cost` for the set covering.
using CapacityWeight = double;
using CapacityWeight = int64_t;
// Term index in a capacity constraint.
DEFINE_STRONG_INT_TYPE(CapacityTermIndex, BaseInt);
@@ -65,8 +67,6 @@ class CapacityModel {
min_capacity_(min),
max_capacity_(max) {
// At least one bound must be set. Otherwise, the constraint is vacuous.
CHECK(!std::isnan(min_capacity_));
CHECK(!std::isnan(max_capacity_));
CHECK(min_capacity_ != std::numeric_limits<CapacityWeight>::min() ||
max_capacity_ != std::numeric_limits<CapacityWeight>::max());
}
@@ -89,7 +89,6 @@ class CapacityModel {
}
// Adds a new term to the constraint.
// This will CHECK-fail if the weight is infinite or a NaN.
void AddTerm(SubsetIndex subset, ElementIndex element, CapacityWeight weight);
// Returns the element, subset, or capacity of the given term.

View File

@@ -17,7 +17,6 @@
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/set_cover/set_cover_model.h"
namespace operations_research {
namespace {
@@ -30,12 +29,6 @@ TEST(CapacityModel, ConstructorRequiresOneBound) {
"min");
}
TEST(CapacityModel, ConstructorRejectsNaN) {
EXPECT_DEATH(CapacityModel(std::numeric_limits<CapacityWeight>::quiet_NaN(),
std::numeric_limits<CapacityWeight>::quiet_NaN()),
"isnan");
}
TEST(CapacityModel, WithMinimumWeightRequiresNonVacuousMinimum) {
EXPECT_DEATH(CapacityModel::WithMinimumWeight(
std::numeric_limits<CapacityWeight>::min()),
@@ -48,183 +41,95 @@ TEST(CapacityModel, WithMaximumWeightRequiresNonVacuousMaximum) {
"min");
}
TEST(CapacityModel, WithMinimumWeightRejectsNaN) {
EXPECT_DEATH(CapacityModel::WithMinimumWeight(
std::numeric_limits<double>::quiet_NaN()),
"isnan");
}
TEST(CapacityModel, WithMaximumWeightRejectsNaN) {
EXPECT_DEATH(CapacityModel::WithMaximumWeight(
std::numeric_limits<double>::quiet_NaN()),
"isnan");
}
TEST(CapacityModel, SetMinimumCapacityRejectsNaN) {
CapacityModel m(0.0, 1.0);
EXPECT_DEATH(m.SetMinimumCapacity(std::numeric_limits<double>::quiet_NaN()),
"isnan");
}
TEST(CapacityModel, SetMinimumCapacityRejectsPlusInfinity) {
CapacityModel m(0.0, 1.0);
CapacityModel m(0, 1);
EXPECT_DEATH(m.SetMinimumCapacity(std::numeric_limits<CapacityWeight>::max()),
"max");
}
TEST(CapacityModel, SetMaximumCapacityRejectsNaN) {
CapacityModel m(0.0, 1.0);
EXPECT_DEATH(m.SetMaximumCapacity(std::numeric_limits<double>::quiet_NaN()),
"isnan");
}
TEST(CapacityModel, SetMaximumCapacityRejectsMinusInfinity) {
CapacityModel m(0.0, 1.0);
CapacityModel m(0, 1);
EXPECT_DEATH(m.SetMaximumCapacity(std::numeric_limits<CapacityWeight>::min()),
"min");
}
TEST(CapacityModel, AddTermRejectsNaN) {
CapacityModel m(0.0, 1.0);
EXPECT_DEATH(m.AddTerm(SubsetIndex(0), ElementIndex(0),
std::numeric_limits<double>::quiet_NaN()),
"isfinite");
}
TEST(CapacityModel, AddTermRejectsPlusInf) {
CapacityModel m(0.0, 1.0);
EXPECT_DEATH(m.AddTerm(SubsetIndex(0), ElementIndex(0),
std::numeric_limits<double>::infinity()),
"isfinite");
}
TEST(CapacityModel, AddTermRejectsMinusInf) {
CapacityModel m(0.0, 1.0);
EXPECT_DEATH(m.AddTerm(SubsetIndex(0), ElementIndex(0),
-std::numeric_limits<double>::infinity()),
"isfinite");
}
TEST(CapacityModel, ComputeFeasibilityWithNoTerms) {
CapacityModel m(0.0, 1.0);
CapacityModel m(0, 1);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMinimumCapacity(-1.0);
m.SetMinimumCapacity(-1);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMaximumCapacity(0.0);
m.SetMaximumCapacity(0);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMinimumCapacity(-2.0);
m.SetMaximumCapacity(-1.0);
m.SetMinimumCapacity(-2);
m.SetMaximumCapacity(-1);
EXPECT_FALSE(m.ComputeFeasibility());
}
TEST(CapacityModel, ComputeFeasibilityWithOnlyPositiveWeights) {
CapacityModel m(0.0, 1.0);
m.AddTerm(SubsetIndex(0), ElementIndex(0), 1.0);
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2.0);
m.AddTerm(SubsetIndex(0), ElementIndex(2), 3.0);
// Activation bounds: [0.0, 6.0].
CapacityModel m(0, 1);
m.AddTerm(SubsetIndex(0), ElementIndex(0), 1);
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2);
m.AddTerm(SubsetIndex(0), ElementIndex(2), 3);
// Activation bounds: [0, 6].
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMinimumCapacity(-1.0);
m.SetMinimumCapacity(-1);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMaximumCapacity(-1.0);
m.SetMaximumCapacity(-1);
EXPECT_FALSE(m.ComputeFeasibility());
m.SetMaximumCapacity(7.0);
m.SetMaximumCapacity(7);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMinimumCapacity(7.0);
m.SetMinimumCapacity(7);
EXPECT_FALSE(m.ComputeFeasibility());
}
TEST(CapacityModel, ComputeFeasibilityWithOnlyNegativeWeights) {
CapacityModel m(0.0, 1.0);
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1.0);
m.AddTerm(SubsetIndex(0), ElementIndex(1), -2.0);
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3.0);
// Activation bounds: [-6.0, 0.0].
CapacityModel m(0, 1);
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1);
m.AddTerm(SubsetIndex(0), ElementIndex(1), -2);
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3);
// Activation bounds: [-6, 0].
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMaximumCapacity(1.0);
m.SetMaximumCapacity(1);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMinimumCapacity(1.0);
m.SetMinimumCapacity(1);
EXPECT_FALSE(m.ComputeFeasibility());
m.SetMinimumCapacity(-7.0);
m.SetMinimumCapacity(-7);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMaximumCapacity(-7.0);
m.SetMaximumCapacity(-7);
EXPECT_FALSE(m.ComputeFeasibility());
}
TEST(CapacityModel, ComputeFeasibilityWithOnlyMixedWeights) {
CapacityModel m(0.0, 1.0);
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1.0);
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2.0);
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3.0);
// Activation bounds: [-4.0, 2.0].
CapacityModel m(0, 1);
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1);
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2);
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3);
// Activation bounds: [-4, 2].
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMaximumCapacity(3.0);
m.SetMaximumCapacity(3);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMinimumCapacity(3.0);
m.SetMinimumCapacity(3);
EXPECT_FALSE(m.ComputeFeasibility());
m.SetMinimumCapacity(-5.0);
m.SetMinimumCapacity(-5);
EXPECT_TRUE(m.ComputeFeasibility());
m.SetMaximumCapacity(-5.0);
m.SetMaximumCapacity(-5);
EXPECT_FALSE(m.ComputeFeasibility());
}
// TEST(CapacityModel, ImportModelFromProto) {
// CapacityModel m(0.0, 1.0);
// EXPECT_THAT(m.ExportModelAsProto(), EqualsProto(R"pb(min_capacity: 0.0
// max_capacity: 1.0)pb"));
// m.AddTerm(SubsetIndex(0), ElementIndex(0), 1.0);
// EXPECT_THAT(m.ExportModelAsProto(),
// EqualsProto(R"pb(min_capacity: 0.0
// max_capacity: 1.0
// capacity_term {
// subset: 0
// element_weights { element: 0 weight: 1.0 }
// })pb"));
// m.AddTerm(SubsetIndex(0), ElementIndex(1), 1.0);
// EXPECT_THAT(m.ExportModelAsProto(),
// EqualsProto(
// R"pb(min_capacity: 0.0
// max_capacity: 1.0
// capacity_term {
// subset: 0
// element_weights { element: 0 weight: 1.0 }
// element_weights { element: 1 weight: 1.0 }
// })pb"));
// }
// TEST(CapacityModel, ImportModelFromProtoHasCanonicalOrder) {
// // Reverse order for the terms compared to
// // CapacityModel_ImportModelFromProto, same order in the proto.
// CapacityModel m(0.0, 1.0);
// m.AddTerm(SubsetIndex(0), ElementIndex(1), 1.0);
// m.AddTerm(SubsetIndex(0), ElementIndex(0), 1.0);
// EXPECT_THAT(m.ExportModelAsProto(),
// EqualsProto(
// R"pb(min_capacity: 0.0
// max_capacity: 1.0
// capacity_term {
// subset: 0
// element_weights { element: 0 weight: 1.0 }
// element_weights { element: 1 weight: 1.0 }
// })pb"));
// }
} // namespace
} // namespace operations_research

View File

@@ -333,13 +333,6 @@ PYBIND11_MODULE(set_cover, m) {
},
arg("subset"))
.def("recompute", &SetCoverInvariant::Recompute)
.def(
"flip",
[](SetCoverInvariant& invariant, BaseInt subset,
SetCoverInvariant::ConsistencyLevel consistency) {
invariant.Flip(SubsetIndex(subset), consistency);
},
arg("subset"), arg("consistency"))
.def(
"select",
[](SetCoverInvariant& invariant, BaseInt subset,
@@ -418,8 +411,8 @@ PYBIND11_MODULE(set_cover, m) {
})
.def("next_solution",
[](ElementDegreeSolutionGenerator& heuristic,
const std::vector<BaseInt>& focus,
const std::vector<double>& costs) -> bool {
absl::Span<const BaseInt> focus,
absl::Span<const double> costs) -> bool {
return heuristic.NextSolution(
VectorIntToVectorSubsetIndex(focus),
VectorDoubleToSubsetCostVector(costs));
@@ -434,7 +427,7 @@ PYBIND11_MODULE(set_cover, m) {
})
.def("next_solution",
[](LazyElementDegreeSolutionGenerator& heuristic,
const std::vector<BaseInt>& focus) -> bool {
absl::Span<const BaseInt> focus) -> bool {
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus));
})
.def("next_solution",
@@ -459,8 +452,8 @@ PYBIND11_MODULE(set_cover, m) {
num_iterations);
})
.def("next_solution",
[](SteepestSearch& heuristic, const std::vector<BaseInt>& focus,
const std::vector<double>& costs, int num_iterations) -> bool {
[](SteepestSearch& heuristic, absl::Span<const BaseInt> focus,
absl::Span<const double> costs, int num_iterations) -> bool {
return heuristic.NextSolution(
VectorIntToVectorSubsetIndex(focus),
VectorDoubleToSubsetCostVector(costs), num_iterations);

View File

@@ -30,6 +30,7 @@
#include "absl/types/span.h"
#include "ortools/algorithms/adjustable_k_ary_heap.h"
#include "ortools/base/logging.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"
@@ -134,9 +135,9 @@ bool GreedySolutionGenerator::NextSolution(absl::Span<const SubsetIndex> focus,
std::vector<SubsetIndex> subsets_to_remove;
subsets_to_remove.reserve(focus.size());
while (!pq.IsEmpty() || inv_->num_uncovered_elements() > 0) {
LOG_EVERY_N_SEC(INFO, 5)
<< "Queue size: " << pq.heap_size()
<< ", #uncovered elements: " << inv_->num_uncovered_elements();
// LOG_EVERY_N_SEC(INFO, 5)
// << "Queue size: " << pq.heap_size()
// << ", #uncovered elements: " << inv_->num_uncovered_elements();
const SubsetIndex best_subset(pq.TopIndex());
pq.Pop();
inv_->Select(best_subset, CL::kFreeAndUncovered);
@@ -705,7 +706,11 @@ bool GuidedTabuSearch::NextSolution(absl::Span<const SubsetIndex> focus,
UpdatePenalties(focus);
tabu_list_.Add(best_subset);
inv_->Flip(best_subset, CL::kFreeAndUncovered);
if (inv_->is_selected()[best_subset]) {
inv_->Deselect(best_subset, CL::kFreeAndUncovered);
} else {
inv_->Select(best_subset, CL::kFreeAndUncovered);
}
// TODO(user): make the cost computation incremental.
augmented_cost =
std::accumulate(augmented_costs_.begin(), augmented_costs_.end(), 0.0);
@@ -714,9 +719,6 @@ bool GuidedTabuSearch::NextSolution(absl::Span<const SubsetIndex> focus,
<< inv_->cost() << ", best cost = ," << best_cost
<< ", penalized cost = ," << augmented_cost;
if (inv_->cost() < best_cost) {
LOG(INFO) << "Updated best cost, " << "Iteration, " << iteration
<< ", current cost = ," << inv_->cost() << ", best cost = ,"
<< best_cost << ", penalized cost = ," << augmented_cost;
best_cost = inv_->cost();
best_choices = inv_->is_selected();
}
@@ -773,17 +775,19 @@ bool GuidedLocalSearch::NextSolution(absl::Span<const SubsetIndex> focus,
for (int iteration = 0;
!priority_heap_.IsEmpty() && iteration < num_iterations; ++iteration) {
// Improve current solution respective to the current penalties.
// Improve current solution respective to the current penalties by flipping
// the best subset.
const SubsetIndex best_subset(priority_heap_.TopIndex());
if (inv_->is_selected()[best_subset]) {
utility_heap_.Insert({0, best_subset.value()});
inv_->Deselect(best_subset, CL::kRedundancy);
} else {
utility_heap_.Insert(
{static_cast<float>(inv_->model()->subset_costs()[best_subset] /
(1 + penalties_[best_subset])),
best_subset.value()});
inv_->Select(best_subset, CL::kRedundancy);
}
inv_->Flip(best_subset, CL::kRedundancy); // Flip the best subset.
DCHECK(!utility_heap_.IsEmpty());
// Getting the subset with highest utility. utility_heap_ is not empty,

View File

@@ -18,8 +18,8 @@
#include "absl/types/span.h"
#include "ortools/algorithms/adjustable_k_ary_heap.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"
namespace operations_research {

View File

@@ -23,6 +23,7 @@
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/mathutil.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_model.h"
namespace operations_research {
@@ -301,15 +302,6 @@ BaseInt SetCoverInvariant::ComputeNumFreeElements(SubsetIndex subset) const {
return num_free_elements;
}
void SetCoverInvariant::Flip(SubsetIndex subset,
ConsistencyLevel target_consistency) {
if (!is_selected_[subset]) {
Select(subset, target_consistency);
} else {
Deselect(subset, target_consistency);
}
}
void SetCoverInvariant::Select(SubsetIndex subset,
ConsistencyLevel target_consistency) {
const bool update_redundancy_info = target_consistency >= CL::kRedundancy;

View File

@@ -19,6 +19,7 @@
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover.pb.h"
#include "ortools/set_cover/set_cover_model.h"
@@ -179,11 +180,6 @@ class SetCoverInvariant {
// Computes the number of free (uncovered) elements in the given subset.
BaseInt ComputeNumFreeElements(SubsetIndex subset) const;
// Flips is_selected_[subset] to its negation, by calling Select or Deselect
// depending on value. Updates the invariant incrementally to the given
// consistency level.
void Flip(SubsetIndex subset, ConsistencyLevel consistency);
// Includes subset in the solution by setting is_selected_[subset] to true
// and incrementally updating the invariant to the given consistency level.
void Select(SubsetIndex subset, ConsistencyLevel consistency);

View File

@@ -23,6 +23,7 @@
#include "absl/synchronization/blocking_counter.h"
#include "ortools/algorithms/adjustable_k_ary_heap.h"
#include "ortools/base/threadpool.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"

View File

@@ -15,11 +15,11 @@
#define OR_TOOLS_SET_COVER_SET_COVER_LAGRANGIAN_H_
#include <memory>
#include <new>
#include <tuple>
#include <vector>
#include "ortools/base/threadpool.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"

View File

@@ -21,6 +21,7 @@
#include "absl/types/span.h"
#include "ortools/linear_solver/linear_solver.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"

View File

@@ -15,8 +15,8 @@
#define OR_TOOLS_SET_COVER_SET_COVER_MIP_H_
#include "absl/types/span.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"
namespace operations_research {
enum class SetCoverMipSolver : int {

View File

@@ -32,6 +32,7 @@
#include "absl/strings/str_format.h"
#include "absl/types/span.h"
#include "ortools/algorithms/radix_sort.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover.pb.h"
namespace operations_research {
@@ -159,15 +160,10 @@ SetCoverModel SetCoverModel::GenerateRandomModelFrom(
subset_already_contains_element[element] = false;
}
}
LOG(INFO) << "Finished generating the model with " << num_elements_covered
<< " elements covered.";
// It can happen -- rarely in practice -- that some of the elements cannot be
// covered. Let's add them to randomly chosen subsets.
if (num_elements_covered != num_elements) {
LOG(INFO) << "Generated model with " << num_elements - num_elements_covered
<< " elements that cannot be covered. Adding them to random "
"subsets.";
SubsetBoolVector element_already_in_subset(num_subsets, false);
for (ElementIndex element(0); element.value() < num_elements; ++element) {
LOG_EVERY_N_SEC(INFO, 5) << absl::StrFormat(
@@ -200,11 +196,7 @@ SetCoverModel SetCoverModel::GenerateRandomModelFrom(
++num_elements_covered;
}
}
LOG(INFO) << "Finished generating subsets for elements that were not "
"covered in the original model.";
}
LOG(INFO) << "Finished generating the model. There are "
<< num_elements - num_elements_covered << " uncovered elements.";
CHECK_EQ(num_elements_covered, num_elements);
@@ -338,10 +330,8 @@ void SetCoverModel::SortElementsInSubsets() {
void SetCoverModel::CreateSparseRowView() {
if (row_view_is_valid_) {
LOG(INFO) << "CreateSparseRowView: already valid";
return;
}
LOG(INFO) << "CreateSparseRowView started";
rows_.resize(num_elements_, SparseRow());
ElementToIntVector row_sizes(num_elements_, 0);
for (const SubsetIndex subset : SubsetRange()) {
@@ -367,7 +357,6 @@ void SetCoverModel::CreateSparseRowView() {
}
row_view_is_valid_ = true;
elements_in_subsets_are_sorted_ = true;
LOG(INFO) << "CreateSparseRowView finished";
}
bool SetCoverModel::ComputeFeasibility() const {
@@ -382,7 +371,7 @@ bool SetCoverModel::ComputeFeasibility() const {
}
SubsetIndex column_index(0);
for (const SparseColumn& column : columns_) {
DLOG_IF(INFO, column.empty()) << "Empty column " << column_index.value();
// DLOG_IF(INFO, column.empty()) << "Empty column " << column_index.value();
for (const ElementIndex element : column) {
++coverage[element];
}
@@ -420,7 +409,6 @@ SetCoverProto SetCoverModel::ExportModelAsProto() const {
subset_proto->add_element(element.value());
}
}
LOG(INFO) << "Finished exporting the model.";
return message;
}

View File

@@ -22,6 +22,7 @@
#include "absl/strings/str_cat.h"
#include "ortools/base/strong_int.h"
#include "ortools/base/strong_vector.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover.pb.h"
// Representation class for the weighted set-covering problem.
@@ -50,55 +51,6 @@
// cardinalities of all the subsets.
namespace operations_research {
// Basic non-strict type for cost. The speed penalty for using double is ~2%.
using Cost = double;
// Base non-strict integer type for counting elements and subsets.
// Using ints makes it possible to represent problems with more than 2 billion
// (2e9) elements and subsets. If need arises one day, BaseInt can be split
// into SubsetBaseInt and ElementBaseInt.
// Quick testing has shown a slowdown of about 20-25% when using int64_t.
using BaseInt = int32_t;
// We make heavy use of strong typing to avoid obvious mistakes.
// Subset index.
DEFINE_STRONG_INT_TYPE(SubsetIndex, BaseInt);
// Element index.
DEFINE_STRONG_INT_TYPE(ElementIndex, BaseInt);
// Position in a vector. The vector may either represent a column, i.e. a
// subset with all its elements, or a row, i,e. the list of subsets which
// contain a given element.
DEFINE_STRONG_INT_TYPE(ColumnEntryIndex, BaseInt);
DEFINE_STRONG_INT_TYPE(RowEntryIndex, BaseInt);
using SubsetRange = util_intops::StrongIntRange<SubsetIndex>;
using ElementRange = util_intops::StrongIntRange<ElementIndex>;
using ColumnEntryRange = util_intops::StrongIntRange<ColumnEntryIndex>;
using SubsetCostVector = util_intops::StrongVector<SubsetIndex, Cost>;
using ElementCostVector = util_intops::StrongVector<ElementIndex, Cost>;
using SparseColumn = util_intops::StrongVector<ColumnEntryIndex, ElementIndex>;
using SparseRow = util_intops::StrongVector<RowEntryIndex, SubsetIndex>;
using ElementToIntVector = util_intops::StrongVector<ElementIndex, BaseInt>;
using SubsetToIntVector = util_intops::StrongVector<SubsetIndex, BaseInt>;
// Views of the sparse vectors. These need not be aligned as it's their contents
// that need to be aligned.
using SparseColumnView = util_intops::StrongVector<SubsetIndex, SparseColumn>;
using SparseRowView = util_intops::StrongVector<ElementIndex, SparseRow>;
using SubsetBoolVector = util_intops::StrongVector<SubsetIndex, bool>;
using ElementBoolVector = util_intops::StrongVector<ElementIndex, bool>;
// Useful for representing permutations,
using ElementToElementVector =
util_intops::StrongVector<ElementIndex, ElementIndex>;
using SubsetToSubsetVector =
util_intops::StrongVector<SubsetIndex, SubsetIndex>;
// Main class for describing a weighted set-covering problem.
class SetCoverModel {

View File

@@ -34,6 +34,7 @@
#include "ortools/base/filesystem.h"
#include "ortools/base/helpers.h"
#include "ortools/base/options.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover.pb.h"
#include "ortools/set_cover/set_cover_model.h"
#include "ortools/util/filelineiter.h"

View File

@@ -23,6 +23,7 @@
#include "absl/time/time.h"
#include "ortools/base/init_google.h"
#include "ortools/base/timer.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover_heuristics.h"
#include "ortools/set_cover/set_cover_invariant.h"
#include "ortools/set_cover/set_cover_model.h"

View File

@@ -22,6 +22,7 @@
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/parse_text_proto.h"
#include "ortools/set_cover/base_types.h"
#include "ortools/set_cover/set_cover.pb.h"
#include "ortools/set_cover/set_cover_heuristics.h"
#include "ortools/set_cover/set_cover_invariant.h"