algorithms: backport from main
This commit is contained in:
@@ -11,9 +11,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
@@ -127,6 +127,20 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "duplicate_remover",
|
||||
srcs = ["duplicate_remover.cc"],
|
||||
hdrs = ["duplicate_remover.h"],
|
||||
deps = [
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/numeric:bits",
|
||||
"@com_google_absl//absl/random",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
"@com_google_absl//absl/types:span",
|
||||
"@com_google_protobuf//:protobuf",
|
||||
],
|
||||
)
|
||||
|
||||
# Hungarian algorithm
|
||||
cc_library(
|
||||
name = "hungarian",
|
||||
@@ -167,20 +181,45 @@ cc_library(
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"//ortools/base:stl_util",
|
||||
# We don't link any underlying solver to let the linear_solver_knapsack
|
||||
# decide what solvers to include.
|
||||
"//ortools/linear_solver",
|
||||
"//ortools/sat:cp_model",
|
||||
"//ortools/sat:cp_model_cc_proto",
|
||||
"//ortools/sat:cp_model_solver",
|
||||
"//ortools/util:bitset",
|
||||
"//ortools/util:time_limit",
|
||||
],
|
||||
)
|
||||
|
||||
# Weighted set covering
|
||||
cc_test(
|
||||
name = "knapsack_solver_test",
|
||||
size = "medium",
|
||||
srcs = ["knapsack_solver_test.cc"],
|
||||
deps = [
|
||||
":knapsack_solver_lib", # buildcleaner: keep
|
||||
"//ortools/base",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/util:time_limit",
|
||||
],
|
||||
)
|
||||
|
||||
# Partitioning and splitting of vector<int64_t>.
|
||||
|
||||
# query matching library.
|
||||
|
||||
# Weighted set covering library.
|
||||
|
||||
proto_library(
|
||||
name = "set_cover_proto",
|
||||
srcs = ["set_cover.proto"],
|
||||
deps = ["//ortools/util:int128_proto"],
|
||||
deps = [
|
||||
"//ortools/util:int128_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
@@ -209,7 +248,6 @@ cc_library(
|
||||
":set_cover_cc_proto",
|
||||
":set_cover_model",
|
||||
"//ortools/base",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/types:span",
|
||||
@@ -258,6 +296,21 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_reader",
|
||||
srcs = ["set_cover_reader.cc"],
|
||||
hdrs = ["set_cover_reader.h"],
|
||||
deps = [
|
||||
":set_cover_model",
|
||||
"//ortools/base:file",
|
||||
"//ortools/util:filelineiter",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:string_view",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "set_cover_test",
|
||||
size = "medium",
|
||||
@@ -291,7 +344,6 @@ cc_library(
|
||||
srcs = ["dynamic_partition.cc"],
|
||||
hdrs = ["dynamic_partition.h"],
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"//ortools/base:murmur",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
@@ -300,6 +352,19 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "dynamic_partition_test",
|
||||
srcs = ["dynamic_partition_test.cc"],
|
||||
deps = [
|
||||
":dynamic_partition",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:stl_util",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/random",
|
||||
"@com_google_absl//absl/random:bit_gen_ref",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "sparse_permutation",
|
||||
srcs = ["sparse_permutation.cc"],
|
||||
@@ -307,6 +372,18 @@ cc_library(
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "sparse_permutation_test",
|
||||
srcs = ["sparse_permutation_test.cc"],
|
||||
deps = [
|
||||
":sparse_permutation",
|
||||
"//ortools/base:gmock_main",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -339,7 +416,6 @@ cc_library(
|
||||
":dynamic_partition",
|
||||
":dynamic_permutation",
|
||||
":sparse_permutation",
|
||||
"//ortools/base",
|
||||
"//ortools/base:dump_vars",
|
||||
"//ortools/base:murmur",
|
||||
"//ortools/graph",
|
||||
@@ -368,13 +444,15 @@ cc_test(
|
||||
":dynamic_permutation",
|
||||
":find_graph_symmetries",
|
||||
":sparse_permutation",
|
||||
"//ortools/base",
|
||||
"//ortools/base:dump_vars",
|
||||
"//ortools/base:file",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:map_util",
|
||||
"//ortools/base:path",
|
||||
"//ortools/graph:io",
|
||||
"//ortools/graph:random_graph",
|
||||
"//ortools/graph:util",
|
||||
"@com_google_absl//absl/numeric:bits",
|
||||
"@com_google_absl//absl/random",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
@@ -383,3 +461,20 @@ cc_test(
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "binary_indexed_tree",
|
||||
hdrs = ["binary_indexed_tree.h"],
|
||||
deps = [
|
||||
"@com_google_absl//absl/log:check",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "binary_indexed_tree_test",
|
||||
srcs = ["binary_indexed_tree_test.cc"],
|
||||
deps = [
|
||||
":binary_indexed_tree",
|
||||
"//ortools/base:gmock_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/log_severity.h"
|
||||
#include "absl/numeric/int128.h"
|
||||
|
||||
@@ -1282,7 +1282,7 @@ int64_t KnapsackMIPSolver::Solve(TimeLimit* /*time_limit*/,
|
||||
// ----- KnapsackCpSat -----
|
||||
class KnapsackCpSat : public BaseKnapsackSolver {
|
||||
public:
|
||||
explicit KnapsackCpSat(const std::string& solver_name);
|
||||
explicit KnapsackCpSat(absl::string_view solver_name);
|
||||
|
||||
// Initializes the solver and enters the problem to be solved.
|
||||
void Init(const std::vector<int64_t>& profits,
|
||||
@@ -1305,7 +1305,7 @@ class KnapsackCpSat : public BaseKnapsackSolver {
|
||||
std::vector<bool> best_solution_;
|
||||
};
|
||||
|
||||
KnapsackCpSat::KnapsackCpSat(const std::string& solver_name)
|
||||
KnapsackCpSat::KnapsackCpSat(absl::string_view solver_name)
|
||||
: BaseKnapsackSolver(solver_name),
|
||||
profits_(),
|
||||
weights_(),
|
||||
|
||||
@@ -1,473 +0,0 @@
|
||||
// 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 "ortools/algorithms/knapsack_solver_for_cuts.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/logging.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
|
||||
const int kNoSelection(-1);
|
||||
const double kInfinity = std::numeric_limits<double>::infinity();
|
||||
|
||||
// Comparator used to sort item in decreasing efficiency order
|
||||
// (see KnapsackCapacityPropagator).
|
||||
struct CompareKnapsackItemsInDecreasingEfficiencyOrder {
|
||||
explicit CompareKnapsackItemsInDecreasingEfficiencyOrder(double _profit_max)
|
||||
: profit_max(_profit_max) {}
|
||||
bool operator()(const KnapsackItemForCutsPtr& item1,
|
||||
const KnapsackItemForCutsPtr& item2) const {
|
||||
return item1->GetEfficiency(profit_max) > item2->GetEfficiency(profit_max);
|
||||
}
|
||||
const double profit_max;
|
||||
};
|
||||
|
||||
// Comparator used to sort search nodes in the priority queue in order
|
||||
// to pop first the node with the highest profit upper bound
|
||||
// (see KnapsackSearchNodeForCuts). When two nodes have the same upper bound, we
|
||||
// prefer the one with the highest current profit. This is usually the one
|
||||
// closer to a leaf. In practice, the main advantage is to have smaller path.
|
||||
struct CompareKnapsackSearchNodePtrInDecreasingUpperBoundOrder {
|
||||
bool operator()(const KnapsackSearchNodeForCuts* node_1,
|
||||
const KnapsackSearchNodeForCuts* node_2) const {
|
||||
const double profit_upper_bound_1 = node_1->profit_upper_bound();
|
||||
const double profit_upper_bound_2 = node_2->profit_upper_bound();
|
||||
if (profit_upper_bound_1 == profit_upper_bound_2) {
|
||||
return node_1->current_profit() < node_2->current_profit();
|
||||
}
|
||||
return profit_upper_bound_1 < profit_upper_bound_2;
|
||||
}
|
||||
};
|
||||
|
||||
using SearchQueue = std::priority_queue<
|
||||
KnapsackSearchNodeForCuts*, std::vector<KnapsackSearchNodeForCuts*>,
|
||||
CompareKnapsackSearchNodePtrInDecreasingUpperBoundOrder>;
|
||||
|
||||
} // namespace
|
||||
|
||||
// ----- KnapsackSearchNodeForCuts -----
|
||||
KnapsackSearchNodeForCuts::KnapsackSearchNodeForCuts(
|
||||
const KnapsackSearchNodeForCuts* const parent,
|
||||
const KnapsackAssignmentForCuts& assignment)
|
||||
: depth_(parent == nullptr ? 0 : parent->depth() + 1),
|
||||
parent_(parent),
|
||||
assignment_(assignment),
|
||||
current_profit_(0),
|
||||
profit_upper_bound_(kInfinity),
|
||||
next_item_id_(kNoSelection) {}
|
||||
|
||||
// ----- KnapsackSearchPathForCuts -----
|
||||
KnapsackSearchPathForCuts::KnapsackSearchPathForCuts(
|
||||
const KnapsackSearchNodeForCuts* from, const KnapsackSearchNodeForCuts* to)
|
||||
: from_(from), via_(nullptr), to_(to) {}
|
||||
|
||||
void KnapsackSearchPathForCuts::Init() {
|
||||
const KnapsackSearchNodeForCuts* node_from =
|
||||
MoveUpToDepth(from_, to_->depth());
|
||||
const KnapsackSearchNodeForCuts* node_to = MoveUpToDepth(to_, from_->depth());
|
||||
DCHECK_EQ(node_from->depth(), node_to->depth());
|
||||
|
||||
// Find common parent.
|
||||
while (node_from != node_to) {
|
||||
node_from = node_from->parent();
|
||||
node_to = node_to->parent();
|
||||
}
|
||||
via_ = node_from;
|
||||
}
|
||||
|
||||
const KnapsackSearchNodeForCuts* MoveUpToDepth(
|
||||
const KnapsackSearchNodeForCuts* node, int depth) {
|
||||
while (node->depth() > depth) {
|
||||
node = node->parent();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// ----- KnapsackStateForCuts -----
|
||||
KnapsackStateForCuts::KnapsackStateForCuts() : is_bound_(), is_in_() {}
|
||||
|
||||
void KnapsackStateForCuts::Init(int number_of_items) {
|
||||
is_bound_.assign(number_of_items, false);
|
||||
is_in_.assign(number_of_items, false);
|
||||
}
|
||||
|
||||
// Returns false when the state is invalid.
|
||||
bool KnapsackStateForCuts::UpdateState(
|
||||
bool revert, const KnapsackAssignmentForCuts& assignment) {
|
||||
if (revert) {
|
||||
is_bound_[assignment.item_id] = false;
|
||||
} else {
|
||||
if (is_bound_[assignment.item_id] &&
|
||||
is_in_[assignment.item_id] != assignment.is_in) {
|
||||
return false;
|
||||
}
|
||||
is_bound_[assignment.item_id] = true;
|
||||
is_in_[assignment.item_id] = assignment.is_in;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----- KnapsackPropagatorForCuts -----
|
||||
KnapsackPropagatorForCuts::KnapsackPropagatorForCuts(
|
||||
const KnapsackStateForCuts* state)
|
||||
: items_(),
|
||||
current_profit_(0),
|
||||
profit_lower_bound_(0),
|
||||
profit_upper_bound_(kInfinity),
|
||||
state_(state) {}
|
||||
|
||||
KnapsackPropagatorForCuts::~KnapsackPropagatorForCuts() = default;
|
||||
|
||||
void KnapsackPropagatorForCuts::Init(absl::Span<const double> profits,
|
||||
absl::Span<const double> weights,
|
||||
const double capacity) {
|
||||
const int number_of_items = profits.size();
|
||||
items_.clear();
|
||||
|
||||
for (int i = 0; i < number_of_items; ++i) {
|
||||
items_.emplace_back(
|
||||
std::make_unique<KnapsackItemForCuts>(i, weights[i], profits[i]));
|
||||
}
|
||||
capacity_ = capacity;
|
||||
current_profit_ = 0;
|
||||
profit_lower_bound_ = -kInfinity;
|
||||
profit_upper_bound_ = kInfinity;
|
||||
InitPropagator();
|
||||
}
|
||||
|
||||
bool KnapsackPropagatorForCuts::Update(
|
||||
bool revert, const KnapsackAssignmentForCuts& assignment) {
|
||||
if (assignment.is_in) {
|
||||
if (revert) {
|
||||
current_profit_ -= items_[assignment.item_id]->profit;
|
||||
consumed_capacity_ -= items()[assignment.item_id]->weight;
|
||||
} else {
|
||||
current_profit_ += items_[assignment.item_id]->profit;
|
||||
consumed_capacity_ += items()[assignment.item_id]->weight;
|
||||
if (consumed_capacity_ > capacity_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void KnapsackPropagatorForCuts::CopyCurrentStateToSolution(
|
||||
std::vector<bool>* solution) const {
|
||||
DCHECK(solution != nullptr);
|
||||
for (int i(0); i < items_.size(); ++i) {
|
||||
const int item_id = items_[i]->id;
|
||||
(*solution)[item_id] = state_->is_bound(item_id) && state_->is_in(item_id);
|
||||
}
|
||||
double remaining_capacity = capacity_ - consumed_capacity_;
|
||||
for (const KnapsackItemForCutsPtr& item : sorted_items_) {
|
||||
if (!state().is_bound(item->id)) {
|
||||
if (remaining_capacity >= item->weight) {
|
||||
remaining_capacity -= item->weight;
|
||||
(*solution)[item->id] = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KnapsackPropagatorForCuts::ComputeProfitBounds() {
|
||||
set_profit_lower_bound(current_profit());
|
||||
break_item_id_ = kNoSelection;
|
||||
|
||||
double remaining_capacity = capacity_ - consumed_capacity_;
|
||||
int break_sorted_item_id = kNoSelection;
|
||||
for (int sorted_id(0); sorted_id < sorted_items_.size(); ++sorted_id) {
|
||||
if (!state().is_bound(sorted_items_[sorted_id]->id)) {
|
||||
const KnapsackItemForCutsPtr& item = sorted_items_[sorted_id];
|
||||
break_item_id_ = item->id;
|
||||
if (remaining_capacity >= item->weight) {
|
||||
remaining_capacity -= item->weight;
|
||||
set_profit_lower_bound(profit_lower_bound() + item->profit);
|
||||
} else {
|
||||
break_sorted_item_id = sorted_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_profit_upper_bound(profit_lower_bound());
|
||||
// If break_sorted_item_id == kNoSelection, then all remaining items fit into
|
||||
// the knapsack, and thus the lower bound on the profit equals the upper
|
||||
// bound. Otherwise, we compute a tight upper bound by filling the remaining
|
||||
// capacity of the knapsack with "fractional" items, in the decreasing order
|
||||
// of their efficiency.
|
||||
if (break_sorted_item_id != kNoSelection) {
|
||||
const double additional_profit =
|
||||
GetAdditionalProfitUpperBound(remaining_capacity, break_sorted_item_id);
|
||||
set_profit_upper_bound(profit_upper_bound() + additional_profit);
|
||||
}
|
||||
}
|
||||
|
||||
void KnapsackPropagatorForCuts::InitPropagator() {
|
||||
consumed_capacity_ = 0;
|
||||
break_item_id_ = kNoSelection;
|
||||
sorted_items_.clear();
|
||||
sorted_items_.reserve(items().size());
|
||||
for (int i(0); i < items().size(); ++i) {
|
||||
sorted_items_.emplace_back(std::make_unique<KnapsackItemForCuts>(
|
||||
i, items()[i]->weight, items()[i]->profit));
|
||||
}
|
||||
profit_max_ = 0;
|
||||
for (const KnapsackItemForCutsPtr& item : sorted_items_) {
|
||||
profit_max_ = std::max(profit_max_, item->profit);
|
||||
}
|
||||
profit_max_ += 1.0;
|
||||
CompareKnapsackItemsInDecreasingEfficiencyOrder compare_object(profit_max_);
|
||||
std::sort(sorted_items_.begin(), sorted_items_.end(), compare_object);
|
||||
}
|
||||
|
||||
double KnapsackPropagatorForCuts::GetAdditionalProfitUpperBound(
|
||||
double remaining_capacity, int break_item_id) const {
|
||||
const int after_break_item_id = break_item_id + 1;
|
||||
double additional_profit_when_no_break_item = 0;
|
||||
if (after_break_item_id < sorted_items_.size()) {
|
||||
// As items are sorted by decreasing profit / weight ratio, and the current
|
||||
// weight is non-zero, the next_weight is non-zero too.
|
||||
const double next_weight = sorted_items_[after_break_item_id]->weight;
|
||||
const double next_profit = sorted_items_[after_break_item_id]->profit;
|
||||
additional_profit_when_no_break_item =
|
||||
std::max((remaining_capacity * next_profit) / next_weight, 0.0);
|
||||
}
|
||||
|
||||
const int before_break_item_id = break_item_id - 1;
|
||||
double additional_profit_when_break_item = 0;
|
||||
if (before_break_item_id >= 0) {
|
||||
const double previous_weight = sorted_items_[before_break_item_id]->weight;
|
||||
// Having previous_weight == 0 means the total capacity is smaller than
|
||||
// the weight of the current item. In such a case the item cannot be part
|
||||
// of a solution of the local one dimension problem.
|
||||
if (previous_weight != 0) {
|
||||
const double previous_profit =
|
||||
sorted_items_[before_break_item_id]->profit;
|
||||
const double overused_capacity =
|
||||
sorted_items_[break_item_id]->weight - remaining_capacity;
|
||||
const double lost_profit_from_previous_item =
|
||||
(overused_capacity * previous_profit) / previous_weight;
|
||||
additional_profit_when_break_item = std::max(
|
||||
sorted_items_[break_item_id]->profit - lost_profit_from_previous_item,
|
||||
0.0);
|
||||
}
|
||||
}
|
||||
|
||||
const double additional_profit = std::max(
|
||||
additional_profit_when_no_break_item, additional_profit_when_break_item);
|
||||
return additional_profit;
|
||||
}
|
||||
|
||||
// ----- KnapsackSolverForCuts -----
|
||||
KnapsackSolverForCuts::KnapsackSolverForCuts(std::string solver_name)
|
||||
: propagator_(&state_),
|
||||
best_solution_profit_(0),
|
||||
solver_name_(std::move(solver_name)) {}
|
||||
|
||||
void KnapsackSolverForCuts::Init(absl::Span<const double> profits,
|
||||
absl::Span<const double> weights,
|
||||
const double capacity) {
|
||||
const int number_of_items(profits.size());
|
||||
state_.Init(number_of_items);
|
||||
best_solution_.assign(number_of_items, false);
|
||||
CHECK_EQ(number_of_items, weights.size());
|
||||
|
||||
propagator_.Init(profits, weights, capacity);
|
||||
}
|
||||
|
||||
void KnapsackSolverForCuts::GetLowerAndUpperBoundWhenItem(int item_id,
|
||||
bool is_item_in,
|
||||
double* lower_bound,
|
||||
double* upper_bound) {
|
||||
DCHECK(lower_bound != nullptr);
|
||||
DCHECK(upper_bound != nullptr);
|
||||
KnapsackAssignmentForCuts assignment(item_id, is_item_in);
|
||||
const bool fail = !IncrementalUpdate(false, assignment);
|
||||
if (fail) {
|
||||
*lower_bound = 0;
|
||||
*upper_bound = 0;
|
||||
} else {
|
||||
*lower_bound = propagator_.profit_lower_bound();
|
||||
*upper_bound = GetAggregatedProfitUpperBound();
|
||||
}
|
||||
|
||||
const bool fail_revert = !IncrementalUpdate(true, assignment);
|
||||
if (fail_revert) {
|
||||
*lower_bound = 0;
|
||||
*upper_bound = 0;
|
||||
}
|
||||
}
|
||||
|
||||
double KnapsackSolverForCuts::Solve(TimeLimit* time_limit,
|
||||
bool* is_solution_optimal) {
|
||||
DCHECK(time_limit != nullptr);
|
||||
DCHECK(is_solution_optimal != nullptr);
|
||||
best_solution_profit_ = 0;
|
||||
*is_solution_optimal = true;
|
||||
|
||||
SearchQueue search_queue;
|
||||
const KnapsackAssignmentForCuts assignment(kNoSelection, true);
|
||||
auto root_node =
|
||||
std::make_unique<KnapsackSearchNodeForCuts>(nullptr, assignment);
|
||||
root_node->set_current_profit(GetCurrentProfit());
|
||||
root_node->set_profit_upper_bound(GetAggregatedProfitUpperBound());
|
||||
root_node->set_next_item_id(GetNextItemId());
|
||||
search_nodes_.push_back(std::move(root_node));
|
||||
const KnapsackSearchNodeForCuts* current_node =
|
||||
search_nodes_.back().get(); // Start with the root node.
|
||||
|
||||
if (MakeNewNode(*current_node, false)) {
|
||||
search_queue.push(search_nodes_.back().get());
|
||||
}
|
||||
if (MakeNewNode(*current_node, true)) {
|
||||
search_queue.push(search_nodes_.back().get());
|
||||
}
|
||||
|
||||
int64_t number_of_nodes_visited = 0;
|
||||
while (!search_queue.empty() &&
|
||||
search_queue.top()->profit_upper_bound() > best_solution_profit_) {
|
||||
if (time_limit->LimitReached()) {
|
||||
*is_solution_optimal = false;
|
||||
break;
|
||||
}
|
||||
if (solution_upper_bound_threshold_ > -kInfinity &&
|
||||
GetAggregatedProfitUpperBound() < solution_upper_bound_threshold_) {
|
||||
*is_solution_optimal = false;
|
||||
break;
|
||||
}
|
||||
if (best_solution_profit_ > solution_lower_bound_threshold_) {
|
||||
*is_solution_optimal = false;
|
||||
break;
|
||||
}
|
||||
if (number_of_nodes_visited >= node_limit_) {
|
||||
*is_solution_optimal = false;
|
||||
break;
|
||||
}
|
||||
KnapsackSearchNodeForCuts* const node = search_queue.top();
|
||||
search_queue.pop();
|
||||
|
||||
if (node != current_node) {
|
||||
KnapsackSearchPathForCuts path(current_node, node);
|
||||
path.Init();
|
||||
CHECK_EQ(UpdatePropagators(path), true);
|
||||
current_node = node;
|
||||
}
|
||||
number_of_nodes_visited++;
|
||||
|
||||
if (MakeNewNode(*node, false)) {
|
||||
search_queue.push(search_nodes_.back().get());
|
||||
}
|
||||
if (MakeNewNode(*node, true)) {
|
||||
search_queue.push(search_nodes_.back().get());
|
||||
}
|
||||
}
|
||||
return best_solution_profit_;
|
||||
}
|
||||
|
||||
// Returns false when at least one propagator fails.
|
||||
bool KnapsackSolverForCuts::UpdatePropagators(
|
||||
const KnapsackSearchPathForCuts& path) {
|
||||
bool no_fail = true;
|
||||
// Revert previous changes.
|
||||
const KnapsackSearchNodeForCuts* node = &path.from();
|
||||
const KnapsackSearchNodeForCuts* const via = &path.via();
|
||||
while (node != via) {
|
||||
no_fail = IncrementalUpdate(true, node->assignment()) && no_fail;
|
||||
node = node->parent();
|
||||
}
|
||||
// Apply current changes.
|
||||
node = &path.to();
|
||||
while (node != via) {
|
||||
no_fail = IncrementalUpdate(false, node->assignment()) && no_fail;
|
||||
node = node->parent();
|
||||
}
|
||||
return no_fail;
|
||||
}
|
||||
|
||||
double KnapsackSolverForCuts::GetAggregatedProfitUpperBound() {
|
||||
propagator_.ComputeProfitBounds();
|
||||
const double propagator_upper_bound = propagator_.profit_upper_bound();
|
||||
return std::min(kInfinity, propagator_upper_bound);
|
||||
}
|
||||
|
||||
bool KnapsackSolverForCuts::MakeNewNode(const KnapsackSearchNodeForCuts& node,
|
||||
bool is_in) {
|
||||
if (node.next_item_id() == kNoSelection) {
|
||||
return false;
|
||||
}
|
||||
KnapsackAssignmentForCuts assignment(node.next_item_id(), is_in);
|
||||
KnapsackSearchNodeForCuts new_node(&node, assignment);
|
||||
|
||||
KnapsackSearchPathForCuts path(&node, &new_node);
|
||||
path.Init();
|
||||
const bool no_fail = UpdatePropagators(path);
|
||||
if (no_fail) {
|
||||
new_node.set_current_profit(GetCurrentProfit());
|
||||
new_node.set_profit_upper_bound(GetAggregatedProfitUpperBound());
|
||||
new_node.set_next_item_id(GetNextItemId());
|
||||
UpdateBestSolution();
|
||||
}
|
||||
|
||||
// Revert to be able to create another node from parent.
|
||||
KnapsackSearchPathForCuts revert_path(&new_node, &node);
|
||||
revert_path.Init();
|
||||
UpdatePropagators(revert_path);
|
||||
|
||||
if (!no_fail || new_node.profit_upper_bound() < best_solution_profit_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The node is relevant.
|
||||
auto relevant_node =
|
||||
std::make_unique<KnapsackSearchNodeForCuts>(&node, assignment);
|
||||
relevant_node->set_current_profit(new_node.current_profit());
|
||||
relevant_node->set_profit_upper_bound(new_node.profit_upper_bound());
|
||||
relevant_node->set_next_item_id(new_node.next_item_id());
|
||||
search_nodes_.push_back(std::move(relevant_node));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KnapsackSolverForCuts::IncrementalUpdate(
|
||||
bool revert, const KnapsackAssignmentForCuts& assignment) {
|
||||
// Do not stop on a failure: To be able to be incremental on the update,
|
||||
// partial solution (state) and propagators must all be in the same state.
|
||||
bool no_fail = state_.UpdateState(revert, assignment);
|
||||
no_fail = propagator_.Update(revert, assignment) && no_fail;
|
||||
return no_fail;
|
||||
}
|
||||
|
||||
void KnapsackSolverForCuts::UpdateBestSolution() {
|
||||
const double profit_lower_bound = propagator_.profit_lower_bound();
|
||||
|
||||
if (best_solution_profit_ < profit_lower_bound) {
|
||||
best_solution_profit_ = profit_lower_bound;
|
||||
propagator_.CopyCurrentStateToSolution(&best_solution_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
@@ -1,388 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// This library solves 0-1 one-dimensional knapsack problems with fractional
|
||||
// profits and weights using the branch and bound algorithm. Note that
|
||||
// algorithms/knapsack_solver uses 'int64_t' for the profits and the weights.
|
||||
// TODO(user): Merge this code with algorithms/knapsack_solver.
|
||||
//
|
||||
// Given n items, each with a profit and a weight and a knapsack of
|
||||
// capacity c, the goal is to find a subset of the items which fits inside c
|
||||
// and maximizes the total profit.
|
||||
// Without loss of generality, profits and weights are assumed to be positive.
|
||||
//
|
||||
// From a mathematical point of view, the one-dimensional knapsack problem
|
||||
// can be modeled by linear constraint:
|
||||
// Sum(i:1..n)(weight_i * item_i) <= c,
|
||||
// where item_i is a 0-1 integer variable.
|
||||
// The goal is to maximize: Sum(i:1..n)(profit_i * item_i).
|
||||
//
|
||||
// Example Usage:
|
||||
// std::vector<double> profits = {0, 0.5, 0.4, 1, 1, 1.1};
|
||||
// std::vector<double> weights = {9, 6, 2, 1.5, 1.5, 1.5};
|
||||
// KnapsackSolverForCuts solver("solver");
|
||||
// solver.Init(profits, weights, capacity);
|
||||
// bool is_solution_optimal = false;
|
||||
// std::unique_ptr<TimeLimit> time_limit =
|
||||
// std::make_unique<TimeLimit>(time_limit_seconds); // Set the time limit.
|
||||
// const double profit = solver.Solve(time_limit.get(), &is_solution_optimal);
|
||||
// const int number_of_items(profits.size());
|
||||
// for (int item_id(0); item_id < number_of_items; ++item_id) {
|
||||
// solver.best_solution(item_id); // Access the solution.
|
||||
// }
|
||||
|
||||
#ifndef OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_FOR_CUTS_H_
|
||||
#define OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_FOR_CUTS_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/int_type.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/util/time_limit.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// ----- KnapsackAssignmentForCuts -----
|
||||
// KnapsackAssignmentForCuts is a small struct used to pair an item with
|
||||
// its assignment. It is mainly used for search nodes and updates.
|
||||
struct KnapsackAssignmentForCuts {
|
||||
KnapsackAssignmentForCuts(int item_id, bool is_in)
|
||||
: item_id(item_id), is_in(is_in) {}
|
||||
|
||||
int item_id;
|
||||
bool is_in;
|
||||
};
|
||||
|
||||
// ----- KnapsackItemForCuts -----
|
||||
// KnapsackItemForCuts is a small struct to pair an item weight with its
|
||||
// corresponding profit.
|
||||
// The aim of the knapsack problem is to pack as many valuable items as
|
||||
// possible. A straight forward heuristic is to take those with the greatest
|
||||
// profit-per-unit-weight. This ratio is called efficiency in this
|
||||
// implementation. So items will be grouped in vectors, and sorted by
|
||||
// decreasing efficiency.
|
||||
struct KnapsackItemForCuts {
|
||||
KnapsackItemForCuts(int id, double weight, double profit)
|
||||
: id(id), weight(weight), profit(profit) {}
|
||||
|
||||
double GetEfficiency(double profit_max) const {
|
||||
return (weight > 0) ? profit / weight : profit_max;
|
||||
}
|
||||
|
||||
// The 'id' field is used to retrieve the initial item in order to
|
||||
// communicate with other propagators and state.
|
||||
const int id;
|
||||
const double weight;
|
||||
const double profit;
|
||||
};
|
||||
using KnapsackItemForCutsPtr = std::unique_ptr<KnapsackItemForCuts>;
|
||||
|
||||
// ----- KnapsackSearchNodeForCuts -----
|
||||
// KnapsackSearchNodeForCuts is a class used to describe a decision in the
|
||||
// decision search tree.
|
||||
// The node is defined by a pointer to the parent search node and an
|
||||
// assignment (see KnapsackAssignmentForCuts).
|
||||
// As the current state is not explicitly stored in a search node, one should
|
||||
// go through the search tree to incrementally build a partial solution from
|
||||
// a previous search node.
|
||||
class KnapsackSearchNodeForCuts {
|
||||
public:
|
||||
KnapsackSearchNodeForCuts(const KnapsackSearchNodeForCuts* parent,
|
||||
const KnapsackAssignmentForCuts& assignment);
|
||||
|
||||
KnapsackSearchNodeForCuts(const KnapsackSearchNodeForCuts&) = delete;
|
||||
KnapsackSearchNodeForCuts& operator=(const KnapsackSearchNodeForCuts&) =
|
||||
delete;
|
||||
|
||||
int depth() const { return depth_; }
|
||||
const KnapsackSearchNodeForCuts* parent() const { return parent_; }
|
||||
const KnapsackAssignmentForCuts& assignment() const { return assignment_; }
|
||||
|
||||
double current_profit() const { return current_profit_; }
|
||||
void set_current_profit(double profit) { current_profit_ = profit; }
|
||||
|
||||
double profit_upper_bound() const { return profit_upper_bound_; }
|
||||
void set_profit_upper_bound(double profit) { profit_upper_bound_ = profit; }
|
||||
|
||||
int next_item_id() const { return next_item_id_; }
|
||||
void set_next_item_id(int id) { next_item_id_ = id; }
|
||||
|
||||
private:
|
||||
// 'depth_' is used to navigate efficiently through the search tree.
|
||||
int depth_;
|
||||
const KnapsackSearchNodeForCuts* const parent_;
|
||||
KnapsackAssignmentForCuts assignment_;
|
||||
|
||||
// 'current_profit_' and 'profit_upper_bound_' fields are used to sort search
|
||||
// nodes using a priority queue. That allows to pop the node with the best
|
||||
// upper bound, and more importantly to stop the search when optimality is
|
||||
// proved.
|
||||
double current_profit_;
|
||||
double profit_upper_bound_;
|
||||
|
||||
// 'next_item_id_' field allows to avoid an O(number_of_items) scan to find
|
||||
// next item to select. This is done for free by the upper bound computation.
|
||||
int next_item_id_;
|
||||
};
|
||||
|
||||
// ----- KnapsackSearchPathForCuts -----
|
||||
// KnapsackSearchPathForCuts is a small class used to represent the path between
|
||||
// a node to another node in the search tree.
|
||||
// As the solution state is not stored for each search node, the state should
|
||||
// be rebuilt at each node. One simple solution is to apply all decisions
|
||||
// between the node 'to' and the root. This can be computed in
|
||||
// O(number_of_items).
|
||||
//
|
||||
// However, it is possible to achieve better average complexity. Two
|
||||
// consecutively explored nodes are usually close enough (i.e., much less than
|
||||
// number_of_items) to benefit from an incremental update from the node
|
||||
// 'from' to the node 'to'.
|
||||
//
|
||||
// The 'via' field is the common parent of 'from' field and 'to' field.
|
||||
// So the state can be built by reverting all decisions from 'from' to 'via'
|
||||
// and then applying all decisions from 'via' to 'to'.
|
||||
class KnapsackSearchPathForCuts {
|
||||
public:
|
||||
KnapsackSearchPathForCuts(const KnapsackSearchNodeForCuts* from,
|
||||
const KnapsackSearchNodeForCuts* to);
|
||||
|
||||
KnapsackSearchPathForCuts(const KnapsackSearchPathForCuts&) = delete;
|
||||
KnapsackSearchPathForCuts& operator=(const KnapsackSearchPathForCuts&) =
|
||||
delete;
|
||||
|
||||
void Init();
|
||||
const KnapsackSearchNodeForCuts& from() const { return *from_; }
|
||||
const KnapsackSearchNodeForCuts& via() const { return *via_; }
|
||||
const KnapsackSearchNodeForCuts& to() const { return *to_; }
|
||||
|
||||
private:
|
||||
const KnapsackSearchNodeForCuts* from_;
|
||||
const KnapsackSearchNodeForCuts* via_; // Computed in 'Init'.
|
||||
const KnapsackSearchNodeForCuts* to_;
|
||||
};
|
||||
|
||||
// From the given node, this method moves up the tree and returns the node at
|
||||
// given depth.
|
||||
const KnapsackSearchNodeForCuts* MoveUpToDepth(
|
||||
const KnapsackSearchNodeForCuts* node, int depth);
|
||||
|
||||
// ----- KnapsackStateForCuts -----
|
||||
// KnapsackStateForCuts represents a partial solution to the knapsack problem.
|
||||
class KnapsackStateForCuts {
|
||||
public:
|
||||
KnapsackStateForCuts();
|
||||
|
||||
KnapsackStateForCuts(const KnapsackStateForCuts&) = delete;
|
||||
KnapsackStateForCuts& operator=(const KnapsackStateForCuts&) = delete;
|
||||
|
||||
// Initializes vectors with number_of_items set to false (i.e. not bound yet).
|
||||
void Init(int number_of_items);
|
||||
|
||||
// Updates the state by applying or reverting a decision.
|
||||
// Returns false if fails, i.e. trying to apply an inconsistent decision
|
||||
// to an already assigned item.
|
||||
bool UpdateState(bool revert, const KnapsackAssignmentForCuts& assignment);
|
||||
|
||||
int GetNumberOfItems() const { return is_bound_.size(); }
|
||||
bool is_bound(int id) const { return is_bound_.at(id); }
|
||||
bool is_in(int id) const { return is_in_.at(id); }
|
||||
|
||||
private:
|
||||
// Vectors 'is_bound_' and 'is_in_' contain a boolean value for each item.
|
||||
// 'is_bound_(item_i)' is false when there is no decision for item_i yet.
|
||||
// When item_i is bound, 'is_in_(item_i)' represents the presence (true) or
|
||||
// the absence (false) of item_i in the current solution.
|
||||
std::vector<bool> is_bound_;
|
||||
std::vector<bool> is_in_;
|
||||
};
|
||||
|
||||
// ----- KnapsackPropagatorForCuts -----
|
||||
// KnapsackPropagatorForCuts is used to enforce a capacity constraint.
|
||||
// It is supposed to compute profit lower and upper bounds, and get the next
|
||||
// item to select, it can be seen as a 0-1 Knapsack solver. The most efficient
|
||||
// way to compute the upper bound is to iterate on items in
|
||||
// profit-per-unit-weight decreasing order. The break item is commonly defined
|
||||
// as the first item for which there is not enough remaining capacity. Selecting
|
||||
// this break item as the next-item-to-assign usually gives the best results
|
||||
// (see Greenberg & Hegerich).
|
||||
//
|
||||
// This is exactly what is implemented in this class.
|
||||
//
|
||||
// It is possible to compute a better profit lower bound almost for free. During
|
||||
// the scan to find the break element all unbound items are added just as if
|
||||
// they were part of the current solution. This is used in both
|
||||
// ComputeProfitBounds() and CopyCurrentSolution(). For incrementality reasons,
|
||||
// the ith item should be accessible in O(1). That's the reason why the item
|
||||
// vector has to be duplicated 'sorted_items_'.
|
||||
class KnapsackPropagatorForCuts {
|
||||
public:
|
||||
explicit KnapsackPropagatorForCuts(const KnapsackStateForCuts* state);
|
||||
~KnapsackPropagatorForCuts();
|
||||
|
||||
KnapsackPropagatorForCuts(const KnapsackPropagatorForCuts&) = delete;
|
||||
KnapsackPropagatorForCuts& operator=(const KnapsackPropagatorForCuts&) =
|
||||
delete;
|
||||
|
||||
// Initializes the data structure and then calls InitPropagator.
|
||||
void Init(absl::Span<const double> profits, absl::Span<const double> weights,
|
||||
double capacity);
|
||||
|
||||
// Updates data structure. Returns false on failure.
|
||||
bool Update(bool revert, const KnapsackAssignmentForCuts& assignment);
|
||||
// ComputeProfitBounds should set 'profit_lower_bound_' and
|
||||
// 'profit_upper_bound_' which are constraint specific.
|
||||
void ComputeProfitBounds();
|
||||
// Returns the id of next item to assign.
|
||||
// Returns kNoSelection when all items are bound.
|
||||
int GetNextItemId() const { return break_item_id_; }
|
||||
|
||||
double current_profit() const { return current_profit_; }
|
||||
double profit_lower_bound() const { return profit_lower_bound_; }
|
||||
double profit_upper_bound() const { return profit_upper_bound_; }
|
||||
|
||||
// Copies the current state into 'solution'.
|
||||
// All unbound items are set to false (i.e. not in the knapsack).
|
||||
void CopyCurrentStateToSolution(std::vector<bool>* solution) const;
|
||||
|
||||
// Initializes the propagator. This method is called by Init() after filling
|
||||
// the fields defined in this class.
|
||||
void InitPropagator();
|
||||
|
||||
const KnapsackStateForCuts& state() const { return *state_; }
|
||||
const std::vector<KnapsackItemForCutsPtr>& items() const { return items_; }
|
||||
|
||||
void set_profit_lower_bound(double profit) { profit_lower_bound_ = profit; }
|
||||
void set_profit_upper_bound(double profit) { profit_upper_bound_ = profit; }
|
||||
|
||||
private:
|
||||
// An obvious additional profit upper bound corresponds to the linear
|
||||
// relaxation: remaining_capacity * efficiency of the break item.
|
||||
// It is possible to do better in O(1), using Martello-Toth bound U2.
|
||||
// The main idea is to enforce integrality constraint on the break item,
|
||||
// i.e. either the break item is part of the solution, or it is not.
|
||||
// So basically the linear relaxation is done on the item before the break
|
||||
// item, or the one after the break item. This is what GetAdditionalProfit
|
||||
// method implements.
|
||||
double GetAdditionalProfitUpperBound(double remaining_capacity,
|
||||
int break_item_id) const;
|
||||
|
||||
double capacity_;
|
||||
double consumed_capacity_;
|
||||
int break_item_id_;
|
||||
std::vector<KnapsackItemForCutsPtr> sorted_items_;
|
||||
double profit_max_;
|
||||
std::vector<KnapsackItemForCutsPtr> items_;
|
||||
double current_profit_;
|
||||
double profit_lower_bound_;
|
||||
double profit_upper_bound_;
|
||||
const KnapsackStateForCuts* const state_;
|
||||
};
|
||||
|
||||
// ----- KnapsackSolverForCuts -----
|
||||
// KnapsackSolverForCuts is the one-dimensional knapsack solver class.
|
||||
// In the current implementation, the next item to assign is given by the
|
||||
// primary propagator. Using SetPrimaryPropagator allows changing the default
|
||||
// (propagator of the first dimension).
|
||||
class KnapsackSolverForCuts {
|
||||
public:
|
||||
explicit KnapsackSolverForCuts(std::string solver_name);
|
||||
|
||||
KnapsackSolverForCuts(const KnapsackSolverForCuts&) = delete;
|
||||
KnapsackSolverForCuts& operator=(const KnapsackSolverForCuts&) = delete;
|
||||
|
||||
// Initializes the solver and enters the problem to be solved.
|
||||
void Init(absl::Span<const double> profits, absl::Span<const double> weights,
|
||||
double capacity);
|
||||
int GetNumberOfItems() const { return state_.GetNumberOfItems(); }
|
||||
|
||||
// Gets the lower and the upper bound when the item is in or out of the
|
||||
// knapsack. To ensure objects are correctly initialized, this method should
|
||||
// not be called before Init().
|
||||
void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in,
|
||||
double* lower_bound, double* upper_bound);
|
||||
|
||||
// Get the best upper bound found so far.
|
||||
double GetUpperBound() { return GetAggregatedProfitUpperBound(); }
|
||||
|
||||
// The solver stops if a solution with profit better than
|
||||
// 'solution_lower_bound_threshold' is found.
|
||||
void set_solution_lower_bound_threshold(
|
||||
const double solution_lower_bound_threshold) {
|
||||
solution_lower_bound_threshold_ = solution_lower_bound_threshold;
|
||||
}
|
||||
|
||||
// The solver stops if the upper bound on profit drops below
|
||||
// 'solution_upper_bound_threshold'.
|
||||
void set_solution_upper_bound_threshold(
|
||||
const double solution_upper_bound_threshold) {
|
||||
solution_upper_bound_threshold_ = solution_upper_bound_threshold;
|
||||
}
|
||||
|
||||
// Stops the knapsack solver after processing 'node_limit' nodes.
|
||||
void set_node_limit(const int64_t node_limit) { node_limit_ = node_limit; }
|
||||
|
||||
// Solves the problem and returns the profit of the best solution found.
|
||||
double Solve(TimeLimit* time_limit, bool* is_solution_optimal);
|
||||
// Returns true if the item 'item_id' is packed in the optimal knapsack.
|
||||
bool best_solution(int item_id) const {
|
||||
DCHECK(item_id < best_solution_.size());
|
||||
return best_solution_[item_id];
|
||||
}
|
||||
|
||||
const std::string& GetName() const { return solver_name_; }
|
||||
|
||||
private:
|
||||
// Updates propagator reverting/applying all decision on the path. Returns
|
||||
// true if the propagation fails. Note that even if it fails, propagator
|
||||
// should be updated to be in a stable state in order to stay incremental.
|
||||
bool UpdatePropagators(const KnapsackSearchPathForCuts& path);
|
||||
// Updates propagator reverting/applying one decision. Returns true if
|
||||
// the propagation fails. Note that even if it fails, propagator should
|
||||
// be updated to be in a stable state in order to stay incremental.
|
||||
bool IncrementalUpdate(bool revert,
|
||||
const KnapsackAssignmentForCuts& assignment);
|
||||
// Updates the best solution if the current solution has a better profit.
|
||||
void UpdateBestSolution();
|
||||
|
||||
// Returns true if new relevant search node was added to the nodes array. That
|
||||
// means this node should be added to the search queue too.
|
||||
bool MakeNewNode(const KnapsackSearchNodeForCuts& node, bool is_in);
|
||||
|
||||
// Gets the aggregated (min) profit upper bound among all propagators.
|
||||
double GetAggregatedProfitUpperBound();
|
||||
double GetCurrentProfit() const { return propagator_.current_profit(); }
|
||||
int GetNextItemId() const { return propagator_.GetNextItemId(); }
|
||||
|
||||
KnapsackPropagatorForCuts propagator_;
|
||||
std::vector<std::unique_ptr<KnapsackSearchNodeForCuts>> search_nodes_;
|
||||
KnapsackStateForCuts state_;
|
||||
double best_solution_profit_;
|
||||
std::vector<bool> best_solution_;
|
||||
const std::string solver_name_;
|
||||
double solution_lower_bound_threshold_ =
|
||||
std::numeric_limits<double>::infinity();
|
||||
double solution_upper_bound_threshold_ =
|
||||
-std::numeric_limits<double>::infinity();
|
||||
int64_t node_limit_ = std::numeric_limits<int64_t>::max();
|
||||
};
|
||||
// TODO(user) : Add reduction algorithm.
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_FOR_CUTS_H_
|
||||
@@ -1,341 +0,0 @@
|
||||
// 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 "ortools/algorithms/knapsack_solver_for_cuts.h"
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
|
||||
const int kInvalidSolution = -1;
|
||||
bool IsSolutionValid(const std::vector<double>& profits,
|
||||
const std::vector<double>& weights, const double capacity,
|
||||
const std::vector<bool>& best_solution,
|
||||
double optimal_profit) {
|
||||
double remaining_capacity = capacity;
|
||||
double profit = 0;
|
||||
const int number_of_items(profits.size());
|
||||
for (int item_id(0); item_id < number_of_items; ++item_id) {
|
||||
if (best_solution.at(item_id)) {
|
||||
profit += profits[item_id];
|
||||
remaining_capacity -= weights[item_id];
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining_capacity < 0) {
|
||||
return false;
|
||||
}
|
||||
return profit == optimal_profit;
|
||||
}
|
||||
|
||||
double SolveKnapsackProblem(KnapsackSolverForCuts* solver) {
|
||||
bool is_solution_optimal = false;
|
||||
auto time_limit =
|
||||
std::make_unique<TimeLimit>(std::numeric_limits<double>::infinity());
|
||||
return solver->Solve(time_limit.get(), &is_solution_optimal);
|
||||
}
|
||||
|
||||
TEST(KnapsackSearchNodeForCutsTest, Depth) {
|
||||
KnapsackAssignmentForCuts assignment(0, false);
|
||||
KnapsackSearchNodeForCuts root(nullptr, assignment);
|
||||
EXPECT_EQ(0, root.depth());
|
||||
|
||||
KnapsackSearchNodeForCuts node_0(&root, assignment);
|
||||
EXPECT_EQ(1, node_0.depth());
|
||||
|
||||
KnapsackSearchNodeForCuts node_00(&node_0, assignment);
|
||||
EXPECT_EQ(2, node_00.depth());
|
||||
}
|
||||
|
||||
TEST(KnapsackSearchPathTest, MoveUpToDepth) {
|
||||
KnapsackAssignmentForCuts assignment(0, false);
|
||||
KnapsackSearchNodeForCuts root(nullptr, assignment);
|
||||
KnapsackSearchNodeForCuts node_0(&root, assignment);
|
||||
KnapsackSearchPathForCuts from_root_to_0(&root, &node_0);
|
||||
const KnapsackSearchNodeForCuts* root_ptr = MoveUpToDepth(&node_0, 0);
|
||||
EXPECT_EQ(&root, root_ptr);
|
||||
}
|
||||
|
||||
TEST(KnapsackSearchPathTest, InitAndMoveUpToDepth) {
|
||||
KnapsackAssignmentForCuts assignment(0, false);
|
||||
KnapsackSearchNodeForCuts root(nullptr, assignment);
|
||||
KnapsackSearchNodeForCuts node_0(&root, assignment);
|
||||
KnapsackSearchNodeForCuts node_00(&node_0, assignment);
|
||||
KnapsackSearchNodeForCuts node_01(&node_0, assignment);
|
||||
KnapsackSearchNodeForCuts node_001(&node_00, assignment);
|
||||
KnapsackSearchNodeForCuts node_010(&node_01, assignment);
|
||||
KnapsackSearchNodeForCuts node_0101(&node_010, assignment);
|
||||
KnapsackSearchNodeForCuts node_01011(&node_0101, assignment);
|
||||
|
||||
KnapsackSearchPathForCuts from_01011_to_001(&node_01011, &node_001);
|
||||
const KnapsackSearchNodeForCuts* node_01_ptr = MoveUpToDepth(&node_01011, 2);
|
||||
EXPECT_EQ(&node_01, node_01_ptr);
|
||||
|
||||
from_01011_to_001.Init();
|
||||
EXPECT_EQ(&node_0, &from_01011_to_001.via());
|
||||
|
||||
KnapsackSearchPathForCuts from_001_to_01011(&node_001, &node_01011);
|
||||
from_001_to_01011.Init();
|
||||
EXPECT_EQ(&from_01011_to_001.via(), &from_001_to_01011.via());
|
||||
}
|
||||
|
||||
TEST(KnapsackItemForCutsTest, GetEfficiency) {
|
||||
const int kId(7);
|
||||
const double kWeight = 52;
|
||||
const double kProfit = 130;
|
||||
const double kEfficiency = 2.5;
|
||||
const double kProfitMax = 1000;
|
||||
const double kNullWeight = 0;
|
||||
|
||||
const KnapsackItemForCuts item(kId, kWeight, kProfit);
|
||||
EXPECT_EQ(kId, item.id);
|
||||
EXPECT_EQ(kWeight, item.weight);
|
||||
EXPECT_EQ(kProfit, item.profit);
|
||||
EXPECT_EQ(kEfficiency, item.GetEfficiency(kProfitMax));
|
||||
|
||||
const KnapsackItemForCuts item2(kId, kNullWeight, kProfit);
|
||||
EXPECT_EQ(kProfitMax, item2.GetEfficiency(kProfitMax));
|
||||
}
|
||||
|
||||
TEST(KnapsackStateForCutsTest, Init) {
|
||||
const int kNumberOfItems(12);
|
||||
KnapsackStateForCuts state;
|
||||
state.Init(kNumberOfItems);
|
||||
for (int i(0); i < kNumberOfItems; ++i) {
|
||||
EXPECT_FALSE(state.is_bound(i));
|
||||
}
|
||||
EXPECT_EQ(kNumberOfItems, state.GetNumberOfItems());
|
||||
}
|
||||
|
||||
TEST(KnapsackStateForCutsTest, UpdateState) {
|
||||
const int kNumberOfItems(12);
|
||||
KnapsackStateForCuts state;
|
||||
state.Init(kNumberOfItems);
|
||||
|
||||
const int item_id(7);
|
||||
bool is_in = true;
|
||||
KnapsackAssignmentForCuts assignment1(item_id, is_in);
|
||||
bool no_fail = state.UpdateState(false, assignment1);
|
||||
for (int i(0); i < kNumberOfItems; ++i) {
|
||||
EXPECT_EQ(i == item_id, state.is_bound(i));
|
||||
}
|
||||
EXPECT_EQ(is_in, state.is_in(item_id));
|
||||
EXPECT_TRUE(no_fail);
|
||||
|
||||
is_in = false;
|
||||
KnapsackAssignmentForCuts assignment2(item_id, is_in);
|
||||
no_fail = state.UpdateState(false, assignment2);
|
||||
EXPECT_TRUE(state.is_bound(item_id));
|
||||
EXPECT_FALSE(no_fail);
|
||||
|
||||
no_fail = state.UpdateState(true, assignment2);
|
||||
EXPECT_FALSE(state.is_bound(item_id));
|
||||
EXPECT_TRUE(no_fail);
|
||||
}
|
||||
|
||||
TEST(KnapsackPropagatorForCutsTest, InitAndUpdatePropagator) {
|
||||
const std::vector<double> profits = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
const std::vector<double> weights = {1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const int kNumItems(profits.size());
|
||||
const int kNoSelection(-1);
|
||||
|
||||
KnapsackStateForCuts state;
|
||||
state.Init(kNumItems);
|
||||
|
||||
KnapsackPropagatorForCuts capacity_propagator(&state);
|
||||
capacity_propagator.Init(profits, weights, 2);
|
||||
EXPECT_EQ(kNoSelection, capacity_propagator.GetNextItemId());
|
||||
|
||||
KnapsackAssignmentForCuts assignment1(3, true);
|
||||
EXPECT_TRUE(state.UpdateState(false, assignment1));
|
||||
EXPECT_TRUE(capacity_propagator.Update(false, assignment1));
|
||||
EXPECT_EQ(4, capacity_propagator.current_profit());
|
||||
capacity_propagator.ComputeProfitBounds();
|
||||
EXPECT_EQ(7, capacity_propagator.GetNextItemId());
|
||||
const double kProfit13 = profits[3] + profits[8];
|
||||
EXPECT_EQ(kProfit13, capacity_propagator.profit_lower_bound());
|
||||
EXPECT_EQ(kProfit13, capacity_propagator.profit_upper_bound());
|
||||
|
||||
KnapsackAssignmentForCuts assignment2(8, true);
|
||||
EXPECT_TRUE(state.UpdateState(false, assignment2));
|
||||
EXPECT_TRUE(capacity_propagator.Update(false, assignment2));
|
||||
EXPECT_EQ(kProfit13, capacity_propagator.current_profit());
|
||||
capacity_propagator.ComputeProfitBounds();
|
||||
EXPECT_EQ(7, capacity_propagator.GetNextItemId());
|
||||
EXPECT_EQ(kProfit13, capacity_propagator.profit_lower_bound());
|
||||
EXPECT_EQ(kProfit13, capacity_propagator.profit_upper_bound());
|
||||
|
||||
KnapsackAssignmentForCuts assignment3(5, true);
|
||||
EXPECT_TRUE(state.UpdateState(false, assignment3));
|
||||
EXPECT_FALSE(capacity_propagator.Update(false, assignment3));
|
||||
const double kProfit19 = profits[3] + profits[8] + profits[5];
|
||||
EXPECT_EQ(kProfit19, capacity_propagator.current_profit());
|
||||
|
||||
EXPECT_TRUE(state.UpdateState(true, assignment2));
|
||||
EXPECT_TRUE(capacity_propagator.Update(true, assignment2));
|
||||
const double kProfit10 = profits[3] + profits[5];
|
||||
EXPECT_EQ(kProfit10, capacity_propagator.current_profit());
|
||||
capacity_propagator.ComputeProfitBounds();
|
||||
EXPECT_EQ(8, capacity_propagator.GetNextItemId());
|
||||
EXPECT_EQ(kProfit10, capacity_propagator.profit_lower_bound());
|
||||
EXPECT_EQ(kProfit10, capacity_propagator.profit_upper_bound());
|
||||
}
|
||||
|
||||
TEST(KnapsackSolverForCutsTest, SolveOneDimension) {
|
||||
const std::vector<double> profits = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
const std::vector<double> weights = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 34;
|
||||
const double kOptimalProfit = 34;
|
||||
KnapsackSolverForCuts solver("solver");
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
|
||||
TEST(KnapsackSolverForCutsTest, SolveOneDimensionInfeasible) {
|
||||
const std::vector<double> profits = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
const std::vector<double> weights = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = -1;
|
||||
KnapsackSolverForCuts solver("solver");
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
const int number_of_items(profits.size());
|
||||
std::vector<bool> best_solution(number_of_items, false);
|
||||
for (int item_id(0); item_id < number_of_items; ++item_id) {
|
||||
best_solution.at(item_id) = solver.best_solution(item_id);
|
||||
}
|
||||
EXPECT_FALSE(
|
||||
IsSolutionValid(profits, weights, kCapacity, best_solution, profit));
|
||||
}
|
||||
|
||||
TEST(KnapsackSolverForCutsTest, MultipleSolves) {
|
||||
KnapsackSolverForCuts solver("solver");
|
||||
{
|
||||
const std::vector<double> profits = {1, 2, 3};
|
||||
const std::vector<double> weights = {4, 5, 6};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 10;
|
||||
const double kOptimalProfit = 4;
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
{
|
||||
const std::vector<double> profits = {1, 2, 3, 7};
|
||||
const std::vector<double> weights = {4, 5, 6, 8};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 10;
|
||||
const double kOptimalProfit = 7;
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
{
|
||||
const std::vector<double> profits = {1, 2};
|
||||
const std::vector<double> weights = {4, 5};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 10;
|
||||
const double kOptimalProfit = 3;
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(KnapsackSolverForCutsTest, SolveBigOneDimension) {
|
||||
const std::vector<double> profits = {
|
||||
360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600,
|
||||
38, 48, 147, 78, 256, 63, 17, 120, 164, 432, 35, 92, 110,
|
||||
22, 42, 50, 323, 514, 28, 87, 73, 78, 15, 26, 78, 210,
|
||||
36, 85, 189, 274, 43, 33, 10, 19, 389, 276, 312};
|
||||
const std::vector<double> weights = {
|
||||
7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15,
|
||||
42, 9, 0, 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56,
|
||||
7, 29, 93, 44, 71, 3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 850;
|
||||
const double kOptimalProfit = 7534;
|
||||
KnapsackSolverForCuts solver("solver");
|
||||
{
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
{
|
||||
// Solve with lower bound threshold.
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
solver.set_solution_lower_bound_threshold(100);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_GT(kOptimalProfit, profit);
|
||||
}
|
||||
{
|
||||
// Solve with upper bound threshold.
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
solver.set_solution_upper_bound_threshold(10000);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_GT(kOptimalProfit, profit);
|
||||
}
|
||||
{
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
solver.set_node_limit(1);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_GT(kOptimalProfit, profit);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(KnapsackSolverForCutsTest, SolveOneDimensionFractionalProfits) {
|
||||
const std::vector<double> profits = {0, 0.5, 0.4, 1, 1, 1.1};
|
||||
const std::vector<double> weights = {9, 6, 2, 1, 1, 1};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 4;
|
||||
const double kOptimalProfit = 3.1;
|
||||
KnapsackSolverForCuts solver("solver");
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
|
||||
TEST(KnapsackSolverForCutsTest, SolveOneDimensionFractionalWeights) {
|
||||
const std::vector<double> profits = {0, 1, 1, 1, 1, 2};
|
||||
const std::vector<double> weights = {9, 6, 2, 1.5, 1.5, 1.5};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 4;
|
||||
const double kOptimalProfit = 3;
|
||||
KnapsackSolverForCuts solver("solver");
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
|
||||
TEST(KnapsackSolverForCutsTest, SolveOneDimensionFractional) {
|
||||
const std::vector<double> profits = {0, 0.5, 0.4, 1, 1, 1.1};
|
||||
const std::vector<double> weights = {9, 6, 2, 1.5, 1.5, 1.5};
|
||||
ASSERT_EQ(profits.size(), weights.size());
|
||||
const double kCapacity = 4;
|
||||
const double kOptimalProfit = 2.1;
|
||||
KnapsackSolverForCuts solver("solver");
|
||||
solver.Init(profits, weights, kCapacity);
|
||||
const double profit = SolveKnapsackProblem(&solver);
|
||||
EXPECT_EQ(kOptimalProfit, profit);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research
|
||||
@@ -22,6 +22,7 @@ from ortools.algorithms.python import knapsack_solver
|
||||
|
||||
|
||||
class PyWrapAlgorithmsKnapsackSolverTest(absltest.TestCase):
|
||||
|
||||
def RealSolve(self, profits, weights, capacities, solver_type, use_reduction):
|
||||
solver = knapsack_solver.KnapsackSolver(solver_type, "solver")
|
||||
solver.set_use_reduction(use_reduction)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "ortools/algorithms/set_cover_mip.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
|
||||
Reference in New Issue
Block a user