algorithms: backport from main

This commit is contained in:
Corentin Le Molgat
2024-03-25 11:20:29 +01:00
parent f88f748635
commit 5bdcc38cbd
8 changed files with 108 additions and 1212 deletions

View File

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

View File

@@ -19,6 +19,7 @@
#include <functional>
#include <limits>
#include <utility>
#include <vector>
#include "absl/base/log_severity.h"
#include "absl/numeric/int128.h"

View File

@@ -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_(),

View File

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

View File

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

View File

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

View File

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

View File

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