diff --git a/examples/cpp/BUILD.bazel b/examples/cpp/BUILD.bazel index 5a73ce605a..52c50f822f 100644 --- a/examples/cpp/BUILD.bazel +++ b/examples/cpp/BUILD.bazel @@ -47,7 +47,10 @@ cc_binary( run_binary_test( name = "binpacking_2d_sat_class01_instance2_test", size = "medium", - args = ["--input $(rootpath //ortools/packing/testdata:Class_01.2bp) --instance 2"], + args = [ + "--input $(rootpath //ortools/packing/testdata:Class_01.2bp)", + "--instance 2", + ], binary = ":binpacking_2d_sat", data = ["//ortools/packing/testdata:Class_01.2bp"], ) @@ -91,21 +94,33 @@ cc_binary( run_binary_test( name = "costas_array_sat_model1_test", size = "medium", - args = ["--minsize=6 --maxsize=6 --model=1"], + args = [ + "--minsize=6", + "--maxsize=6", + "--model=1", + ], binary = ":costas_array_sat", ) run_binary_test( name = "costas_array_sat_model2_test", size = "medium", - args = ["--minsize=6 --maxsize=6 --model=2"], + args = [ + "--minsize=6", + "--maxsize=6", + "--model=2", + ], binary = ":costas_array_sat", ) run_binary_test( name = "costas_array_sat_model3_test", size = "medium", - args = ["--minsize=6 --maxsize=6 --model=3"], + args = [ + "--minsize=6", + "--maxsize=6", + "--model=3", + ], binary = ":costas_array_sat", ) @@ -191,7 +206,10 @@ cc_binary( run_binary_test( name = "knapsack_2d_sat_class01_instance2_test", size = "medium", - args = ["--input $(rootpath //ortools/packing/testdata:Class_01.2bp) --instance 2"], + args = [ + "--input $(rootpath //ortools/packing/testdata:Class_01.2bp)", + "--instance 2", + ], binary = ":knapsack_2d_sat", data = ["//ortools/packing/testdata:Class_01.2bp"], ) diff --git a/ortools/set_cover/samples/BUILD.bazel b/ortools/set_cover/samples/BUILD.bazel new file mode 100644 index 0000000000..b4515ce264 --- /dev/null +++ b/ortools/set_cover/samples/BUILD.bazel @@ -0,0 +1,54 @@ +# Copyright 2010-2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_python//python:py_binary.bzl", "py_binary") +load("//bazel:run_binary_test.bzl", "run_binary_test") + +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "set_cover_cc", + srcs = ["set_cover.cc"], + deps = [ + "//ortools/base", + "//ortools/set_cover:set_cover_heuristics", + "//ortools/set_cover:set_cover_invariant", + "//ortools/set_cover:set_cover_model", + "@abseil-cpp//absl/base:log_severity", + "@abseil-cpp//absl/log", + "@abseil-cpp//absl/log:globals", + ], +) + +run_binary_test( + name = "set_cover_cc_test", + size = "small", + binary = ":set_cover_cc", +) + +py_binary( + name = "set_cover_py3", + srcs = ["set_cover.py"], + main = "set_cover.py", + deps = [ + "//ortools/set_cover:set_cover_py_pb2", + "//ortools/set_cover/python:set_cover", + ], +) + +run_binary_test( + name = "set_cover_py_test", + size = "small", + binary = ":set_cover_py3", +) diff --git a/ortools/set_cover/samples/bin_packing.cc b/ortools/set_cover/samples/bin_packing.cc deleted file mode 100644 index c89a8ee4db..0000000000 --- a/ortools/set_cover/samples/bin_packing.cc +++ /dev/null @@ -1,501 +0,0 @@ - -// Copyright 2025 Francesco Cavaliere -// 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/set_cover/samples/bin_packing.h" - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "ortools/base/stl_util.h" -#include "ortools/set_cover/base_types.h" -#include "ortools/set_cover/set_cover_cft.h" -#include "ortools/set_cover/set_cover_submodel.h" -#include "ortools/util/filelineiter.h" - -namespace operations_research { - -void BinPackingModel::set_bin_capacity(Cost capacity) { - if (capacity <= 0) { - LOG(WARNING) << "Bin capacity must be positive."; - return; - } - bin_capacity_ = capacity; -} -void BinPackingModel::AddItem(Cost weight) { - if (weight > bin_capacity_) { - LOG(WARNING) << "Element weight exceeds bin capacity."; - return; - } - weigths_.push_back(weight); - is_sorted_ = false; -} -void BinPackingModel::SortWeights() { - if (!is_sorted_) { - absl::c_sort(weigths_); - is_sorted_ = true; - } -} - -namespace { -template -bool SimpleAtoValue(absl::string_view str, int_type* out) { - return absl::SimpleAtoi(str, out); -} -bool SimpleAtoValue(absl::string_view str, bool* out) { - return absl::SimpleAtob(str, out); -} -bool SimpleAtoValue(absl::string_view str, double* out) { - return absl::SimpleAtod(str, out); -} -bool SimpleAtoValue(absl::string_view str, float* out) { - return absl::SimpleAtof(str, out); -} -} // namespace - -BinPackingModel ReadBpp(absl::string_view filename) { - BinPackingModel model; - BaseInt num_items = 0; - for (const std::string& line : - FileLines(filename, FileLineIterator::REMOVE_INLINE_CR | - FileLineIterator::REMOVE_BLANK_LINES)) { - if (num_items == 0) { - if (!SimpleAtoValue(line, &num_items)) { - LOG(WARNING) << "Invalid number of elements in file: " << line; - } - continue; - } - - Cost value = .0; - if (!SimpleAtoValue(line, &value)) { - LOG(WARNING) << "Invalid value in file: " << line; - continue; - } - if (model.bin_capacity() <= .0) { - DCHECK_GT(value, .0); - model.set_bin_capacity(value); - } else { - model.AddItem(value); - } - } - DCHECK_GT(model.bin_capacity(), .0); - DCHECK_GT(model.weights().size(), .0); - DCHECK_EQ(num_items, model.weights().size()); - model.SortWeights(); - return model; -} - -BinPackingModel ReadCsp(absl::string_view filename) { - BinPackingModel model; - BaseInt num_item_types = 0; - for (const std::string& line : - FileLines(filename, FileLineIterator::REMOVE_INLINE_CR | - FileLineIterator::REMOVE_BLANK_LINES)) { - if (num_item_types == 0) { - if (!SimpleAtoValue(line, &num_item_types)) { - LOG(WARNING) << "Invalid number of elements in file: " << line; - } - continue; - } - if (model.bin_capacity() <= .0) { - Cost capacity = .0; - if (!SimpleAtoValue(line, &capacity)) { - LOG(WARNING) << "Invalid value in file: " << line; - } - model.set_bin_capacity(capacity); - continue; - } - - std::pair weight_and_demand = - absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty()); - - Cost weight = .0; - if (!SimpleAtoValue(weight_and_demand.first, &weight)) { - LOG(WARNING) << "Invalid weight in file: " << line; - continue; - } - - BaseInt demand = 0; - if (!SimpleAtoValue(weight_and_demand.second, &demand)) { - LOG(WARNING) << "Invalid demand in file: " << line; - continue; - } - for (BaseInt i = 0; i < demand; ++i) { - model.AddItem(weight); - } - } - DCHECK_GT(model.bin_capacity(), .0); - DCHECK_GT(model.weights().size(), .0); - DCHECK_GE(num_item_types, model.num_items()); - model.SortWeights(); - return model; -} - -void BestFit(const ElementCostVector& weights, Cost bin_capacity, - const std::vector& items, PartialBins& bins_data) { - for (ElementIndex item : items) { - Cost item_weight = weights[item]; - BaseInt selected_bin = bins_data.bins.size(); - for (BaseInt bin = 0; bin < bins_data.bins.size(); ++bin) { - Cost max_load = bin_capacity - item_weight; - if (bins_data.loads[bin] <= max_load && - (selected_bin == bins_data.bins.size() || - bins_data.loads[bin] > bins_data.loads[selected_bin])) { - selected_bin = bin; - } - } - if (selected_bin == bins_data.bins.size()) { - bins_data.bins.emplace_back(); - bins_data.loads.emplace_back(); - } - bins_data.bins[selected_bin].push_back(item); - bins_data.loads[selected_bin] += item_weight; - } -} - -const SparseColumn& BinPackingSetCoverModel::BinPackingModelGlobals::GetBin( - SubsetIndex j) const { - if (j < SubsetIndex(full_model.num_subsets())) { - return full_model.columns()[j]; - } - DCHECK(candidate_bin != nullptr); - return *candidate_bin; -} - -uint64_t BinPackingSetCoverModel::BinHash::operator()(SubsetIndex j) const { - DCHECK(globals != nullptr); - return absl::HashOf(globals->GetBin(j)); -} - -uint64_t BinPackingSetCoverModel::BinEq::operator()(SubsetIndex j1, - SubsetIndex j2) const { - DCHECK(globals != nullptr); - return globals->GetBin(j1) == globals->GetBin(j2); -} - -bool BinPackingSetCoverModel::AddBin(const SparseColumn& bin) { - if (TryInsertBin(bin)) { - globals_.full_model.AddEmptySubset(1.0); - for (ElementIndex i : bin) { - globals_.full_model.AddElementToLastSubset(i); - } - return true; - } - return false; -} - -bool BinPackingSetCoverModel::TryInsertBin(const SparseColumn& bin) { - DCHECK(absl::c_is_sorted(bin)); - DCHECK(absl::c_adjacent_find(bin) == bin.end()); - DCHECK(globals_.candidate_bin == nullptr); - globals_.candidate_bin = &bin; - - SubsetIndex candidate_j(globals_.full_model.num_subsets()); - bool inserted = bin_set_.insert(candidate_j).second; - - globals_.candidate_bin = nullptr; - return inserted; -} - -void InsertBinsIntoModel(PartialBins& bins_data, - BinPackingSetCoverModel& model) { - for (BaseInt i = 0; i < bins_data.bins.size(); ++i) { - if (bins_data.bins[i].size() > 0) { - absl::c_sort(bins_data.bins[i]); - model.AddBin(bins_data.bins[i]); - } - } -} - -void AddRandomizedBins(const BinPackingModel& model, BaseInt num_bins, - BinPackingSetCoverModel& scp_model, std::mt19937& rnd) { - PartialBins bins_data; - std::vector items(model.num_items()); - absl::c_iota(items, ElementIndex(0)); - - while (scp_model.full_model().num_subsets() < num_bins) { - // Generate bins all containing a specific item - for (ElementIndex n : model.ItemRange()) { - BaseInt unique_bin_num = scp_model.full_model().num_subsets(); - VLOG_EVERY_N_SEC(1, 1) - << "[RGEN] Generating bins: " << unique_bin_num << " / " << num_bins - << " (" << 100.0 * unique_bin_num / num_bins << "%)"; - if (scp_model.full_model().num_subsets() >= num_bins) { - break; - } - - absl::c_shuffle(items, rnd); - - auto n_it = absl::c_find(items, n); - std::iter_swap(n_it, items.end() - 1); - items.pop_back(); - - bins_data.bins.clear(); - bins_data.loads.clear(); - for (BaseInt j = 0; j < 10; ++j) { - bins_data.bins.push_back({n}); - bins_data.loads.push_back(model.weights()[n]); - } - BestFit(model.weights(), model.bin_capacity(), items, bins_data); - InsertBinsIntoModel(bins_data, scp_model); - - items.push_back(n); - - if (unique_bin_num == scp_model.full_model().num_subsets()) { - LOG(INFO) << "No new bins generated."; - break; - } - } - } - - scp_model.CompleteModel(); -} - -BinPackingSetCoverModel GenerateInitialBins(const BinPackingModel& model) { - BinPackingSetCoverModel scp_model(&model); - PartialBins bins_data; - std::vector items(model.num_items()); - - absl::c_iota(items, ElementIndex(0)); - absl::c_sort(items, [&](auto i1, auto i2) { - return model.weights()[i1] > model.weights()[i2]; - }); - BestFit(model.weights(), model.bin_capacity(), items, bins_data); - InsertBinsIntoModel(bins_data, scp_model); - VLOG(1) << "[BFIT] Largest first best-fit solution: " << bins_data.bins.size() - << " bins"; - - scp_model.CompleteModel(); - return scp_model; -} - -void ExpKnap::SaveBin() { - collected_bins_.back().clear(); - for (ElementIndex i : exceptions_) { - break_selection_[i] = !break_selection_[i]; - } - for (ElementIndex i : break_solution_) { - if (break_selection_[i]) { - collected_bins_.back().push_back(i); - } - } - for (ElementIndex i : exceptions_) { - if (break_selection_[i]) { - collected_bins_.back().push_back(i); - } - break_selection_[i] = !break_selection_[i]; - } - absl::c_sort(collected_bins_.back()); - DCHECK(absl::c_adjacent_find(collected_bins_.back()) == - collected_bins_.back().end()); -} -void ExpKnap::InitSolver(const ElementCostVector& profits, - const ElementCostVector& weights, Cost capacity, - BaseInt bnb_nodes_limit) { - capacity_ = capacity; - items_.resize(profits.size()); - collected_bins_.clear(); - for (ElementIndex i; i < ElementIndex(items_.size()); ++i) { - items_[i] = {profits[i], weights[i], i}; - } - absl::c_sort(items_, [](Item i1, Item i2) { - return i1.profit / i1.weight < i2.profit / i2.weight; - }); - - bnb_node_countdown_ = bnb_nodes_limit; - best_delta_ = .0; - exceptions_.clear(); - break_selection_.assign(profits.size(), false); - break_solution_.clear(); -} - -void ExpKnap::FindGoodColumns(const ElementCostVector& profits, - const ElementCostVector& weights, Cost capacity, - BaseInt bnb_nodes_limit) { - InitSolver(profits, weights, capacity, bnb_nodes_limit); - Cost curr_best_cost = .0; - PartialBins more_bins; - std::vector remaining_items; - - bnb_node_countdown_ = bnb_nodes_limit; - inserted_items_.assign(profits.size(), false); - do { - collected_bins_.emplace_back(); - Heuristic(); - VLOG(5) << "[KPCG] Heuristic solution: cost " - << break_profit_sum_ + best_delta_; - EleBranch(); - - for (ElementIndex i : collected_bins_.back()) { - inserted_items_[i] = true; - } - gtl::STLEraseAllFromSequenceIf( - &items_, [&](Item item) { return inserted_items_[item.index]; }); - } while (!items_.empty() && break_profit_sum_ + best_delta_ > 1.0); -} - -bool ExpKnap::EleBranch() { - exceptions_.clear(); - return EleBranch(.0, break_weight_sum_ - capacity_, break_it_ - 1, break_it_); -} - -namespace { -static Cost BoundCheck(Cost best_delta, Cost profit_delta, Cost overweight, - ExpKnap::Item item) { - Cost bound = best_delta + 0.0; // + 1.0 for integral profits - return (profit_delta - bound) * item.weight - overweight * item.profit; -} -} // namespace - -// Adapted version from David Pisinger elebranch function: -// https://hjemmesider.diku.dk/~pisinger/expknap.c -bool ExpKnap::EleBranch(Cost profit_delta, Cost overweigth, ItemIt out_item, - ItemIt in_item) { - VLOG(6) << "[KPCG] EleBranch: profit_delta " << profit_delta << " overweigth " - << overweigth; - if (bnb_node_countdown_-- <= 0) { - return false; - } - bool improved = false; - - if (overweigth <= .0) { - if (profit_delta > best_delta_) { - best_delta_ = profit_delta; - improved = true; - VLOG(5) << "[KPCG] Improved best cost " - << break_profit_sum_ + best_delta_; - } - - bool maximal = true; - while (bnb_node_countdown_ > 0 && in_item < items_.rend() && - BoundCheck(best_delta_, profit_delta, overweigth, *in_item) >= 0) { - exceptions_.push_back(in_item->index); - Cost next_delta = profit_delta + in_item->profit; - Cost next_oweight = overweigth + in_item->weight; - maximal &= !EleBranch(next_delta, next_oweight, out_item, ++in_item); - exceptions_.pop_back(); - } - - if (improved && maximal) { - SaveBin(); - } - improved |= !maximal; - } else { - while (bnb_node_countdown_ > 0 && out_item >= items_.rbegin() && - BoundCheck(best_delta_, profit_delta, overweigth, *out_item) >= 0) { - exceptions_.push_back(out_item->index); - Cost next_delta = profit_delta - out_item->profit; - Cost next_oweight = overweigth - out_item->weight; - improved |= EleBranch(next_delta, next_oweight, --out_item, in_item); - exceptions_.pop_back(); - } - } - return improved; -} - -void ExpKnap::Heuristic() { - best_delta_ = break_profit_sum_ = break_weight_sum_ = .0; - break_it_ = items_.rbegin(); - break_selection_.assign(items_.size(), false); - break_solution_.clear(); - exceptions_.clear(); - while (break_it_ < items_.rend() && - break_it_->weight <= capacity_ - break_weight_sum_) { - break_profit_sum_ += break_it_->profit; - break_weight_sum_ += break_it_->weight; - break_selection_[break_it_->index] = true; - break_solution_.push_back(break_it_->index); - ++break_it_; - } - Cost residual = capacity_ - break_weight_sum_; - - VLOG(5) << "[KPCG] Break solution: cost " << break_profit_sum_ - << ", residual " << residual; - - Cost profit_delta_ub = residual * break_it_->profit / break_it_->weight; - if (profit_delta_ub == .0) { - SaveBin(); - return; - } - - // Try filling the residual space with less efficient (maybe smaller) items - best_delta_ = .0; - for (auto it = break_it_; it < items_.rend(); it++) { - if (it->weight <= residual && it->profit > best_delta_) { - exceptions_ = {it->index}; - best_delta_ = it->profit; - if (best_delta_ >= profit_delta_ub) { - SaveBin(); - return; - } - } - } - - // Try removing an item and adding the break item - Cost min_weight = break_it_->weight - residual; - for (auto it = break_it_ - 1; it >= items_.rbegin(); it--) { - Cost profit_delta = break_it_->profit - it->profit; - if (it->weight >= min_weight && profit_delta > best_delta_) { - exceptions_ = {break_it_->index, it->index}; - best_delta_ = profit_delta; - if (best_delta_ >= profit_delta_ub) { - SaveBin(); - return; - } - } - } - SaveBin(); -} - -bool BinPackingSetCoverModel::UpdateCore( - Cost best_lower_bound, const ElementCostVector& best_multipliers, - const scp::Solution& best_solution, bool force) { - if (!base::IsTimeToUpdate(best_lower_bound, force)) { - return false; - } - - Cost full_lower_bound = base::UpdateMultipliers(best_multipliers); - if (scp::DivideIfGE0(std::abs(full_lower_bound - best_lower_bound), - best_lower_bound) < 0.01 && - best_lower_bound != prev_lower_bound_) { - prev_lower_bound_ = best_lower_bound; - knapsack_solver_.FindGoodColumns(best_multipliers, bpp_model_->weights(), - bpp_model_->bin_capacity(), - /*bnb_nodes_limit=*/1000); - BaseInt num_added_bins = 0; - for (const SparseColumn& bin : knapsack_solver_.collected_bins()) { - num_added_bins += AddBin(bin) ? 1 : 0; - } - if (num_added_bins > 0) { - VLOG(4) << "[KPCG] Added " << num_added_bins << " / " - << globals_.full_model.num_subsets() << " bins"; - } - base::SizeUpdate(); - // TODO(user): add incremental update only for the new columns just added - base::UpdateMultipliers(best_multipliers); - } - - if (base::num_focus_subsets() < FixingFullModelView().num_focus_subsets()) { - ComputeAndSetFocus(best_lower_bound, best_solution); - return true; - } - return false; -} -} // namespace operations_research diff --git a/ortools/set_cover/samples/bin_packing.h b/ortools/set_cover/samples/bin_packing.h deleted file mode 100644 index 9432ede9d5..0000000000 --- a/ortools/set_cover/samples/bin_packing.h +++ /dev/null @@ -1,180 +0,0 @@ - -// Copyright 2025 Francesco Cavaliere -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef ORTOOLS_SET_COVER_SAMPLES_BIN_PACKING_H -#define ORTOOLS_SET_COVER_SAMPLES_BIN_PACKING_H - -#include -#include -#include -#include -#include - -#include -#include - -#include "ortools/set_cover/base_types.h" -#include "ortools/set_cover/set_cover_cft.h" - -namespace operations_research { - -class BinPackingModel { - public: - BinPackingModel() = default; - BaseInt num_items() const { return weigths_.size(); } - Cost bin_capacity() const { return bin_capacity_; } - void set_bin_capacity(Cost capacity); - const ElementCostVector& weights() const { return weigths_; } - void AddItem(Cost weight); - void SortWeights(); - ElementRange ItemRange() const { - return {ElementIndex(), ElementIndex(weigths_.size())}; - } - - private: - bool is_sorted_ = false; - Cost bin_capacity_ = .0; - ElementCostVector weigths_ = {}; -}; - -struct PartialBins { - std::vector bins; - std::vector loads; -}; - -using SubsetHashVector = util_intops::StrongVector; - -class ExpKnap { - public: - struct Item { - Cost profit; // profit - Cost weight; // weight - ElementIndex index; - }; - using ItemIt = std::vector::const_reverse_iterator; - - void SaveBin(); - void FindGoodColumns(const ElementCostVector& profits, - const ElementCostVector& weights, Cost capacity, - BaseInt bnb_nodes_limit); - void InitSolver(const ElementCostVector& profits, - const ElementCostVector& weights, Cost capacity, - BaseInt bnb_nodes_limit); - - bool EleBranch(); - bool EleBranch(Cost profit_sum, Cost overweight, ItemIt out_item, - ItemIt in_item); - - void Heuristic(); - - const std::vector& collected_bins() const { - return collected_bins_; - } - Cost best_cost() const { return break_profit_sum_ + best_delta_; } - - private: - Cost capacity_; // capacity - util_intops::StrongVector items_; // items - ItemIt break_it_; - Cost break_profit_sum_; - Cost break_weight_sum_; - Cost best_delta_; - std::vector exceptions_; - std::vector collected_bins_; - ElementBoolVector break_selection_; - ElementBoolVector inserted_items_; - SparseColumn break_solution_; - BaseInt bnb_node_countdown_; - std::mt19937 rnd_; -}; - -class BinPackingSetCoverModel : public scp::FullToCoreModel { - using base = scp::FullToCoreModel; - - struct BinPackingModelGlobals { - // Dirty hack to avoid invalidation of pointers/references - // A pointer to this data structure is used to compute the hash of bins - // starting from their indices. - scp::Model full_model; - - // External bins do not have a valid index in the model, a temporary pointer - // is used insteda (even dirtier hack). - const SparseColumn* candidate_bin; - - const SparseColumn& GetBin(SubsetIndex j) const; - }; - - struct BinHash { - const BinPackingModelGlobals* globals; - uint64_t operator()(SubsetIndex j) const; - }; - - struct BinEq { - const BinPackingModelGlobals* globals; - uint64_t operator()(SubsetIndex j1, SubsetIndex j2) const; - }; - - public: - BinPackingSetCoverModel(const BinPackingModel* bpp_model) - : globals_{scp::Model(), nullptr}, - bpp_model_(bpp_model), - knapsack_solver_(), - bin_set_({}, 0, BinHash{&globals_}, BinEq{&globals_}), - column_gen_countdown_(10), - column_gen_period_(10) {} - const scp::Model& full_model() const { return globals_.full_model; } - - bool AddBin(const SparseColumn& bin); - void CompleteModel() { - globals_.full_model.CreateSparseRowView(); - static_cast(*this) = - scp::FullToCoreModel(&globals_.full_model); - } - - bool UpdateCore(Cost best_lower_bound, - const ElementCostVector& best_multipliers, - const scp::Solution& best_solution, bool force) override; - - private: - bool TryInsertBin(const SparseColumn& bin); - - BinPackingModelGlobals globals_; - const BinPackingModel* bpp_model_; - ExpKnap knapsack_solver_; - - // Contains bin indices, but it really should contains "bins" (aka, - // SparseColumn). However to avoid redundant allocations (in scp::Model and - // in the set) we cannot store them also here. We cannot also use - // iterators/pointers/references because they can be invalidated. So we store - // bin indices and do ungodly hacky shenanigans to get the bins from them. - absl::flat_hash_set bin_set_; - - Cost prev_lower_bound_; - BaseInt column_gen_countdown_; - BaseInt column_gen_period_; -}; - -BinPackingModel ReadBpp(absl::string_view filename); -BinPackingModel ReadCsp(absl::string_view filename); - -void BestFit(const BinPackingModel& model, - const std::vector& items, PartialBins& bins_data); - -BinPackingSetCoverModel GenerateInitialBins(const BinPackingModel& model); - -void AddRandomizedBins(const BinPackingModel& model, BaseInt num_bins, - BinPackingSetCoverModel& scp_model, std::mt19937& rnd); - -} // namespace operations_research -#endif /* ORTOOLS_SET_COVER_SAMPLES_BIN_PACKING_H */ diff --git a/ortools/set_cover/samples/bin_packing_cft.cc b/ortools/set_cover/samples/bin_packing_cft.cc deleted file mode 100644 index 4a311d71e1..0000000000 --- a/ortools/set_cover/samples/bin_packing_cft.cc +++ /dev/null @@ -1,144 +0,0 @@ - -// Copyright 2025 Francesco Cavaliere -// 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 -#include - -#include -#include - -#include "ortools/base/init_google.h" -#include "ortools/set_cover/base_types.h" -#include "ortools/set_cover/set_cover_cft.h" -#include "ortools/set_cover/samples/bin_packing.h" - -using namespace operations_research; -ABSL_FLAG(std::string, instance, "", "BPP instance int RAIL format."); -ABSL_FLAG(int, bins, 1000, "Number of bins to generate."); - -template -std::string Stringify(const Iterable& col) { - std::string result; - for (auto i : col) { - absl::StrAppend(&result, " ", i); - } - return result; -} - -bool operator==(const SparseColumn& lhs, const std::vector& rhs) { - if (lhs.size() != rhs.size()) return false; - auto lit = lhs.begin(); - auto rit = rhs.begin(); - while (lit != lhs.end() && rit != rhs.end()) { - if (static_cast(*lit) != *rit) { - return false; - } - ++lit; - ++rit; - } - return true; -} - -void RunTest(const ElementCostVector& weights, const ElementCostVector& profits, - const std::vector& expected) { - ExpKnap knap_solver; - - for (ElementIndex i; i < ElementIndex(weights.size()); ++i) { - std::cout << "Item " << i << " -- profit: " << profits[i] - << " weight: " << weights[i] - << " efficiency: " << profits[i] / weights[i] << "\n"; - } - - knap_solver.InitSolver(profits, weights, 6, 100000000); - knap_solver.Heuristic(); - std::cout << "Heur solution cost " << knap_solver.best_cost() << " -- " - << Stringify(knap_solver.collected_bins()[0]) << "\n"; - - knap_solver.EleBranch(); - std::cout << "B&b solution cost " << knap_solver.best_cost() << " -- " - << Stringify(knap_solver.collected_bins()[0]) << "\n"; - - const auto& result = knap_solver.collected_bins()[0]; - if (!(result == expected)) { - std::cout << "Error: expected " << Stringify(expected) << " but got " - << Stringify(result) << "\n"; - } - std::cout << std::endl; -} - -void KnapsackTest() { - std::cout << "Testing knapsack\n"; - ExpKnap knap_solver; - ElementCostVector ws = {1, 2, 3, 4, 5}; - RunTest(ws, {10, 20, 30, 40, 51}, {0, 4}); - RunTest(ws, {10, 20, 30, 41, 50}, {1, 3}); - RunTest(ws, {10, 20, 31, 40, 50}, {0, 1, 2}); - RunTest(ws, {10, 21, 30, 41, 50}, {1, 3}); - RunTest(ws, {11, 21, 30, 40, 50}, {0, 1, 2}); - RunTest(ws, {11, 20, 31, 40, 50}, {0, 1, 2}); - RunTest(ws, {11, 20, 30, 41, 50}, {0, 4}); - RunTest(ws, {11, 20, 30, 40, 51}, {0, 4}); - RunTest(ws, {11, 21, 31, 40, 50}, {0, 1, 2}); - RunTest({4.1, 2, 2, 2}, {8.5, 3, 3, 3}, {1, 2, 3}); -} - -int main(int argc, char** argv) { - InitGoogle(argv[0], &argc, &argv, true); - absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); - // KnapsackTest(); - // return 0; - - BinPackingModel model = ReadBpp(absl::GetFlag(FLAGS_instance)); - - // Quick run with a minimal set of bins - BinPackingSetCoverModel scp_model = GenerateInitialBins(model); - scp::PrimalDualState best_result = scp::RunCftHeuristic(scp_model); - - if (absl::GetFlag(FLAGS_bins) > 0) { - // Run the CFT again with more bins to get a better solution - std::mt19937 rnd(0); - AddRandomizedBins(model, absl::GetFlag(FLAGS_bins), scp_model, rnd); - scp::PrimalDualState result = - scp::RunCftHeuristic(scp_model, best_result.solution); - if (result.solution.cost() < best_result.solution.cost()) { - best_result = result; - } - } - - auto [solution, dual] = best_result; - if (solution.subsets().empty()) { - std::cerr << "Error: failed to find any solution\n"; - } else { - std::cout << "Solution: " << solution.cost() << '\n'; - } - - if (dual.multipliers().empty()) { - std::cerr << "Error: failed to find any dual\n"; - } else { - std::cout << "Core Lower bound: " << dual.lower_bound() << '\n'; - } - - // The lower bound computed on the full model is not a real lower bound unless - // the knapsack subproblem failed to fined any negative reduced cost bin to - // add to the set cover model. - // TODO(anyone): add a flag to indicate if a valid LB has been found or not. - if (scp_model.best_dual_state().multipliers().empty()) { - std::cerr << "Error: no real dual state has been computed\n"; - } else { - std::cout << "Restricted Lower bound: " - << scp_model.best_dual_state().lower_bound() << '\n'; - } - - return EXIT_SUCCESS; -} diff --git a/ortools/set_cover/samples/cft.cc b/ortools/set_cover/samples/cft.cc deleted file mode 100644 index 90a72ea6b0..0000000000 --- a/ortools/set_cover/samples/cft.cc +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include - -#include "ortools/base/init_google.h" -#include "ortools/set_cover/set_cover_cft.h" -#include "ortools/set_cover/set_cover_reader.h" - -using namespace operations_research; -ABSL_FLAG(std::string, instance, "", "SCP instance int RAIL format."); - -#define DO_PRICING -int main(int argc, char **argv) { - InitGoogle(argv[0], &argc, &argv, true); - - scp::Model original_model = ReadOrlibRail(absl::GetFlag(FLAGS_instance)); - -#ifdef DO_PRICING - scp::FullToCoreModel model(&original_model); -#else - scp::SubModel model(&original_model); -#endif - - scp::PrimalDualState result = scp::RunCftHeuristic(model); - auto [solution, dual] = result; - if (solution.subsets().empty()) { - std::cerr << "Error: failed to find any solution\n"; - } else { - std::cout << "Solution: " << solution.cost() << '\n'; - } - -#ifdef DO_PRICING - if (dual.multipliers().empty()) { - std::cerr << "Error: failed to find any dual\n"; - } else { - std::cout << "Core Lower bound: " << dual.lower_bound() << '\n'; - } - if (model.best_dual_state().multipliers().empty()) { - std::cerr << "Error: no real dual state has been computed\n"; - } else { - std::cout << "Full Lower bound: " << model.best_dual_state().lower_bound() - << '\n'; - } -#else - if (dual.multipliers().empty()) { - std::cerr << "Error: failed to find any dual\n"; - } else { - std::cout << "Lower bound: " << dual.lower_bound() << '\n'; - } -#endif - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/ortools/set_cover/samples/code_samples_cc_test.sh b/ortools/set_cover/samples/code_samples_cc_test.sh deleted file mode 100755 index 996d2d1a74..0000000000 --- a/ortools/set_cover/samples/code_samples_cc_test.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2010-2025 Google LLC -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -source gbash.sh || exit -source module gbash_unit.sh - -DEFINE_string sample "" "sample code." - -function test::operations_research_examples::code_samples_set_cover() { - declare -r DIR="${TEST_SRCDIR}/ortools/set_cover/samples" - EXPECT_SUCCEED "${DIR}/${FLAGS_sample}_cc" -} - -gbash::unit::main "$@" diff --git a/ortools/set_cover/samples/code_samples_py_test.sh b/ortools/set_cover/samples/code_samples_py_test.sh deleted file mode 100755 index a7f44f2844..0000000000 --- a/ortools/set_cover/samples/code_samples_py_test.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2010-2025 Google LLC -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -source gbash.sh || exit -source module gbash_unit.sh - -DEFINE_string sample "" "sample code." - -function test::operations_research_examples::code_samples_set_cover_py() { - declare -r DIR="${TEST_SRCDIR}/ortools/set_cover/samples" - EXPECT_SUCCEED "${DIR}/${FLAGS_sample}_py3" -} - -gbash::unit::main "$@"