Skip to content

Commit

Permalink
Add DP heuristic for onedimensional problems
Browse files Browse the repository at this point in the history
  • Loading branch information
fontanf committed Nov 14, 2024
1 parent a2a34b0 commit f4dee70
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 7 deletions.
6 changes: 0 additions & 6 deletions data/onedimensional/BUILD

This file was deleted.

2 changes: 1 addition & 1 deletion extern/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ FetchContent_MakeAvailable(Boost)
FetchContent_Declare(
knapsacksolver
GIT_REPOSITORY https://github.com/fontanf/knapsacksolver.git
GIT_TAG 5115e4db582c97e2e1ade8ea3ab5e678a921b223
GIT_TAG c4eddb0bd2124ad0598507610d356cb82aa72403
#SOURCE_DIR "${PROJECT_SOURCE_DIR}/../knapsacksolver/"
EXCLUDE_FROM_ALL)
FetchContent_MakeAvailable(knapsacksolver)
Expand Down
3 changes: 3 additions & 0 deletions include/packingsolver/algorithms/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ struct Output: optimizationtools::Output
/** Bin packing bound. */
BinPos bin_packing_bound = 0;

/** Knapsack bound. */
Profit knapsack_bound = std::numeric_limits<Profit>::infinity();

/** Elapsed time. */
double time = 0.0;

Expand Down
14 changes: 14 additions & 0 deletions include/packingsolver/onedimensional/algorithm_formatter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,20 @@ class AlgorithmFormatter
const Solution& solution,
const std::string& s);

/** Update the bin packing bound. */
void update_bin_packing_bound(
BinPos number_of_bins);

/** Update the knapsack bound. */
void update_knapsack_bound(
Profit profit);

/** Method to call at the end of the algorithm. */
void end();

/** Get end boolean. */
bool& end_boolean() { return end_; };

private:

/** Instance. */
Expand All @@ -56,6 +67,9 @@ class AlgorithmFormatter
/** Output stream. */
std::unique_ptr<optimizationtools::ComposeStream> os_;

/** End boolean. */
bool end_ = false;

/** Mutex. */
std::mutex mutex_;

Expand Down
15 changes: 15 additions & 0 deletions include/packingsolver/onedimensional/solution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ struct SolutionBin

/** Stacks. */
std::vector<SolutionItem> items;

/** Maximum number of items allowed in the bin. */
ItemPos maximum_number_of_items = -1;

/**
* Remaining weight allowed in the bin to satisfy the maximum
* weight after constraint.
*/
Weight remaiing_weight = -1;
};

/**
Expand Down Expand Up @@ -126,6 +135,9 @@ class Solution
/** Get the fraction of waste of the solution including the residual. */
inline double full_waste_percentage() const { return (bin_length() == 0)? 0.0: (double)full_waste() / bin_length(); }

/** Return 'true' iff the solution satisfies the packing constraints. */
inline bool feasible() const { return feasible_; }

/** Get the number of copies of an item type in the solution. */
inline ItemPos item_copies(ItemTypeId item_type_id) const { return item_copies_[item_type_id]; }

Expand Down Expand Up @@ -191,6 +203,9 @@ class Solution
/** Number of copies of each item type in the solution. */
std::vector<ItemPos> item_copies_;

/** 'true' iff the solution is feasible regarding the packing constraints. */
bool feasible_ = true;

};

std::ostream& operator<<(
Expand Down
4 changes: 4 additions & 0 deletions src/algorithms/sequential_value_correction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ SequentialValueCorrectionOutput<Instance, Solution> sequential_value_correction(
(BinPos)(item_remaining_copies / item_packed_copies));
}
}
if (number_of_copies < 1) {
throw std::logic_error(
"number_of_copies: " + std::to_string(number_of_copies) + ".");
}

// Update ratio_sums.
//auto item_space = instance.item_type(0).space();
Expand Down
47 changes: 47 additions & 0 deletions src/onedimensional/algorithm_formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,53 @@ void AlgorithmFormatter::update_solution(
print(s);
output_.json["IntermediaryOutputs"].push_back(output_.to_json());
parameters_.new_solution_callback(output_);

// Check optimality.
if (instance_.objective() == Objective::BinPacking) {
if (output_.solution_pool.best().full()
&& output_.bin_packing_bound == output_.solution_pool.best().number_of_bins()) {
end_ = true;
}
} else if (instance_.objective() == Objective::Knapsack) {
if (output_.knapsack_bound == output_.solution_pool.best().profit()) {
end_ = true;
}
}
}
mutex_.unlock();
}

void AlgorithmFormatter::update_bin_packing_bound(
BinPos number_of_bins)
{
mutex_.lock();
if (number_of_bins > output_.bin_packing_bound) {
output_.bin_packing_bound = number_of_bins;
output_.json["IntermediaryOutputs"].push_back(output_.to_json());
parameters_.new_solution_callback(output_);

// Check optimality.
if (output_.solution_pool.best().full()
&& output_.bin_packing_bound == output_.solution_pool.best().number_of_bins()) {
end_ = true;
}
}
mutex_.unlock();
}

void AlgorithmFormatter::update_knapsack_bound(
Profit profit)
{
mutex_.lock();
if (profit < output_.knapsack_bound) {
output_.knapsack_bound = profit;
output_.json["IntermediaryOutputs"].push_back(output_.to_json());
parameters_.new_solution_callback(output_);

// Check optimality.
if (output_.knapsack_bound == output_.solution_pool.best().profit()) {
end_ = true;
}
}
mutex_.unlock();
}
Expand Down
85 changes: 85 additions & 0 deletions src/onedimensional/optimize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

#include "treesearchsolver/iterative_beam_search_2.hpp"

#include "knapsacksolver/knapsack/instance_builder.hpp"
#include "knapsacksolver/knapsack/algorithms/dynamic_programming_primal_dual.hpp"

#include <thread>

using namespace packingsolver;
Expand All @@ -17,11 +20,79 @@ using namespace packingsolver::onedimensional;
namespace
{

void optimize_dynamic_programming(
const Instance& instance,
const OptimizeParameters& parameters,
AlgorithmFormatter& algorithm_formatter)
{
if (instance.number_of_bins() != 1)
return;
if (instance.objective() != Objective::Knapsack)
return;

knapsacksolver::knapsack::InstanceFromFloatProfitsBuilder kp_instance_builder;
std::vector<std::pair<ItemTypeId, ItemPos>> kp2ps;
knapsacksolver::knapsack::Weight kp_capacity = instance.bin_type(0).length;
kp_instance_builder.set_capacity(kp_capacity);
for (ItemTypeId item_type_id = 0;
item_type_id < instance.number_of_item_types();
++item_type_id) {
const ItemType& item_type = instance.item_type(item_type_id);
ItemPos total_copies = 0;
ItemPos copies = 1;
while (total_copies < item_type.copies) {
if (total_copies + copies > item_type.copies)
copies = item_type.copies - total_copies;
knapsacksolver::knapsack::Weight kp_weight = copies * item_type.length;
if (kp_weight > kp_capacity)
break;
kp2ps.push_back({item_type_id, copies});
kp_instance_builder.add_item(
copies * item_type.profit,
kp_weight);
total_copies += copies;
copies *= 2;
}
}
knapsacksolver::knapsack::Instance kp_instance = kp_instance_builder.build();

knapsacksolver::knapsack::DynamicProgrammingPrimalDualParameters kp_parameters;
kp_parameters.verbosity_level = 0;
auto kp_output = knapsacksolver::knapsack::dynamic_programming_primal_dual(
kp_instance,
kp_parameters);

Solution solution(instance);
solution.add_bin(0, 1);
for (knapsacksolver::knapsack::ItemId kp_item_type_id = 0;
kp_item_type_id < kp_instance.number_of_items();
++kp_item_type_id) {
if (kp_output.solution.contains(kp_item_type_id)) {
ItemTypeId item_type_id = kp2ps[kp_item_type_id].first;
ItemPos copies = kp2ps[kp_item_type_id].second;
for (ItemPos copy = 0; copy < copies; ++copy)
solution.add_item(0, item_type_id);
}
}

if (solution.feasible()) {
std::stringstream ss;
ss << "DP";
algorithm_formatter.update_solution(solution, ss.str());
}
algorithm_formatter.update_knapsack_bound(solution.profit());
}

void optimize_tree_search(
const Instance& instance,
const OptimizeParameters& parameters,
AlgorithmFormatter& algorithm_formatter)
{
// Try dynamic programming.
optimize_dynamic_programming(instance, parameters, algorithm_formatter);
if (algorithm_formatter.end_boolean())
return;

std::vector<GuideId> guides;
if (!parameters.tree_search_guides.empty()) {
guides = parameters.tree_search_guides;
Expand Down Expand Up @@ -51,6 +122,8 @@ void optimize_tree_search(
treesearchsolver::IterativeBeamSearch2Parameters<BranchingScheme> ibs_parameters;
ibs_parameters.verbosity_level = 0;
ibs_parameters.timer = parameters.timer;
if (parameters.optimization_mode == OptimizationMode::Anytime)
ibs_parameters.timer.set_end_boolean(&algorithm_formatter.end_boolean());
ibs_parameters.growth_factor = growth_factor;
if (parameters.optimization_mode != OptimizationMode::Anytime) {
ibs_parameters.minimum_size_of_the_queue
Expand Down Expand Up @@ -146,6 +219,8 @@ void optimize_sequential_single_knapsack(
SequentialValueCorrectionParameters<Instance, Solution> svc_parameters;
svc_parameters.verbosity_level = 0;
svc_parameters.timer = parameters.timer;
if (parameters.optimization_mode == OptimizationMode::Anytime)
svc_parameters.timer.set_end_boolean(&algorithm_formatter.end_boolean());
svc_parameters.maximum_number_of_iterations = 1;
svc_parameters.new_solution_callback = [
&algorithm_formatter, &queue_size](
Expand All @@ -160,6 +235,8 @@ void optimize_sequential_single_knapsack(
sequential_value_correction<Instance, InstanceBuilder, Solution, AlgorithmFormatter>(instance, kp_solve, svc_parameters);

// Check end.
if (algorithm_formatter.end_boolean())
break;
if (parameters.timer.needs_to_end())
break;

Expand Down Expand Up @@ -195,6 +272,8 @@ void optimize_sequential_value_correction(
SequentialValueCorrectionParameters<Instance, Solution> svc_parameters;
svc_parameters.verbosity_level = 0;
svc_parameters.timer = parameters.timer;
if (parameters.optimization_mode == OptimizationMode::Anytime)
svc_parameters.timer.set_end_boolean(&algorithm_formatter.end_boolean());
if (parameters.optimization_mode != OptimizationMode::Anytime)
svc_parameters.maximum_number_of_iterations = parameters.not_anytime_sequential_value_correction_number_of_iterations;
svc_parameters.new_solution_callback = [&algorithm_formatter](
Expand Down Expand Up @@ -238,6 +317,8 @@ void optimize_dichotomic_search(
DichotomicSearchParameters<Instance, Solution> ds_parameters;
ds_parameters.verbosity_level = 0;
ds_parameters.timer = parameters.timer;
if (parameters.optimization_mode == OptimizationMode::Anytime)
ds_parameters.timer.set_end_boolean(&algorithm_formatter.end_boolean());
ds_parameters.initial_waste_percentage_upper_bound = waste_percentage_upper_bound;
ds_parameters.new_solution_callback = [
&algorithm_formatter, &queue_size](
Expand All @@ -253,6 +334,8 @@ void optimize_dichotomic_search(
auto ds_output = dichotomic_search<Instance, InstanceBuilder, Solution, AlgorithmFormatter>(instance, bpp_solve, ds_parameters);

// Check end.
if (algorithm_formatter.end_boolean())
break;
if (parameters.timer.needs_to_end())
break;

Expand Down Expand Up @@ -291,6 +374,8 @@ void optimize_column_generation(
columngenerationsolver::LimitedDiscrepancySearchParameters cgslds_parameters;
cgslds_parameters.verbosity_level = 0;
cgslds_parameters.timer = parameters.timer;
if (parameters.optimization_mode == OptimizationMode::Anytime)
cgslds_parameters.timer.set_end_boolean(&algorithm_formatter.end_boolean());
cgslds_parameters.internal_diving = 1;
cgslds_parameters.dummy_column_objective_coefficient = (std::max)(2 * instance.bin_type(0).cost, (Profit)1);
if (parameters.optimization_mode != OptimizationMode::Anytime)
Expand Down
36 changes: 36 additions & 0 deletions src/onedimensional/solution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,55 @@ void Solution::add_item(

SolutionItem item;
item.item_type_id = item_type_id;

item.start = bin.end;
if (!bin.items.empty())
item.start -= item_type.nesting_length;

bin.end = item.start + item_type.length;
if (bin.end > bin_type.length) {
feasible_ = false;
}

// Update bin.weight.
bin.weight += item_type.weight;
if (bin.weight > bin_type.maximum_weight) {
feasible_ = false;
}

bin.items.push_back(item);

// Update bin.maximum_number_of_items and bin.maximum_number_of_items.
if (bin.items.size() == 1) {
bin.maximum_number_of_items = item_type.maximum_stackability;
bin.remaiing_weight = item_type.maximum_weight_after;
} else {
bin.maximum_number_of_items = std::min(
bin.maximum_number_of_items,
item_type.maximum_stackability);
bin.remaiing_weight = std::min(
bin.remaiing_weight - item_type.weight,
item_type.maximum_weight_after);
}
if (bin.items.size() > bin.maximum_number_of_items) {
feasible_ = false;
}
if (bin.remaiing_weight < 0) {
feasible_ = false;
}

number_of_items_ += bin.copies;
item_copies_[item.item_type_id] += bin.copies;
if (item_copies_[item.item_type_id] > item_type.copies) {
throw std::runtime_error(
"onedimensional::Solution::add_item"
"; item_copies_[item.item_type_id]: " + std::to_string(item_copies_[item.item_type_id])
+ "; item_type.copies: " + std::to_string(item_type.copies));
}
item_length_ += bin.copies * item_type.length;
item_profit_ += bin.copies * item_type.profit;

// Update length_.
if (bin_pos == (BinPos)bins_.size() - 1)
length_ = bin_length_ - bin_type.length + bin.end;
}
Expand Down

0 comments on commit f4dee70

Please sign in to comment.