Skip to content

Commit 353a450

Browse files
committed
adding a loader for the bin packing problem
1 parent ca8dce0 commit 353a450

File tree

4 files changed

+362
-0
lines changed

4 files changed

+362
-0
lines changed

libecole/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ add_library(
2222
src/instance/combinatorial-auction.cpp
2323
src/instance/capacitated-facility-location.cpp
2424
src/instance/capacitated-vehicle-routing.cpp
25+
src/instance/bin-packing.cpp
2526

2627
src/reward/is-done.cpp
2728
src/reward/lp-iterations.cpp
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#ifndef BIN_PACKING_HPP
2+
#define BIN_PACKING_HPP
3+
#pragma once
4+
5+
#include <cstddef>
6+
#include <string>
7+
#include <utility>
8+
#include <vector>
9+
10+
#include "ecole/export.hpp"
11+
#include "ecole/instance/abstract.hpp"
12+
#include "ecole/random.hpp"
13+
14+
namespace ecole::instance {
15+
16+
class ECOLE_EXPORT Binpacking : public InstanceGenerator {
17+
public:
18+
struct ECOLE_EXPORT Parameters {
19+
std::string filename; // NOLINT(readability-magic-numbers)
20+
std::size_t n_bins; // NOLINT(readability-magic-numbers)
21+
};
22+
23+
ECOLE_EXPORT static scip::Model generate_instance(Parameters parameters, RandomGenerator& rng);
24+
25+
ECOLE_EXPORT Binpacking(Parameters parameters, RandomGenerator rng);
26+
ECOLE_EXPORT Binpacking(Parameters parameters);
27+
ECOLE_EXPORT Binpacking();
28+
29+
ECOLE_EXPORT scip::Model next() override;
30+
ECOLE_EXPORT void seed(Seed seed) override;
31+
[[nodiscard]] ECOLE_EXPORT bool done() const override { return false; }
32+
33+
[[nodiscard]] ECOLE_EXPORT Parameters const& get_parameters() const noexcept { return parameters; }
34+
35+
private:
36+
RandomGenerator rng;
37+
Parameters parameters;
38+
};
39+
40+
} // namespace ecole::instance
41+
42+
#endif
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
#include <array>
2+
#include <cstddef>
3+
#include <fstream>
4+
#include <iostream>
5+
#include <memory>
6+
#include <scip/type_var.h>
7+
#include <string>
8+
#include <utility>
9+
#include <vector>
10+
11+
#include "ecole/instance/bin-packing.hpp"
12+
#include "ecole/scip/cons.hpp"
13+
#include "ecole/scip/model.hpp"
14+
#include "ecole/scip/utils.hpp"
15+
#include "ecole/scip/var.hpp"
16+
#include <fmt/format.h>
17+
#include <range/v3/view/enumerate.hpp>
18+
#include <xtensor/xadapt.hpp>
19+
#include <xtensor/xbuilder.hpp>
20+
#include <xtensor/xio.hpp>
21+
#include <xtensor/xmath.hpp>
22+
#include <xtensor/xrandom.hpp>
23+
#include <xtensor/xtensor.hpp>
24+
#include <xtensor/xview.hpp>
25+
26+
namespace ecole::instance {
27+
28+
/**************************************************
29+
* Binpacking methods *
30+
**************************************************/
31+
32+
Binpacking::Binpacking(Binpacking::Parameters parameters_, RandomGenerator rng_) :
33+
rng{rng_}, parameters{std::move(parameters_)} {}
34+
Binpacking::Binpacking(Binpacking::Parameters parameters_) : Binpacking{parameters_, ecole::spawn_random_generator()} {}
35+
Binpacking::Binpacking() : Binpacking(Parameters{}) {}
36+
37+
scip::Model Binpacking::next() {
38+
return generate_instance(parameters, rng);
39+
}
40+
41+
void Binpacking::seed(Seed seed) {
42+
rng.seed(seed);
43+
}
44+
45+
/*************************************************************
46+
* Binpacking::generate_instance *
47+
*************************************************************/
48+
49+
namespace {
50+
51+
using value_type = SCIP_Real;
52+
using xvector = xt::xtensor<value_type, 1>;
53+
using xmatrix = xt::xtensor<value_type, 2>;
54+
55+
auto read_problem(
56+
std::string& filename, /**< filename */
57+
int& n_items, /**< capacity in instance */
58+
int& capacity,
59+
std::vector<double>& weights /**< array of demands of instance */
60+
) {
61+
62+
SCIP_FILE* file;
63+
SCIP_Bool error;
64+
char name[SCIP_MAXSTRLEN];
65+
char format[16];
66+
char buffer[SCIP_MAXSTRLEN];
67+
int bestsolvalue;
68+
int nread;
69+
int weight;
70+
int n_weights;
71+
int lineno;
72+
73+
file = SCIPfopen(filename.c_str(), "r");
74+
/* open file */
75+
if (file == NULL) {
76+
std::cerr << fmt::format("cannot open file <{}> for reading\n", filename);
77+
SCIPprintSysError(filename.c_str());
78+
return SCIP_NOFILE;
79+
}
80+
81+
lineno = 0;
82+
std::cout << name << "++ uninitialized ++";
83+
84+
/* read problem name */
85+
if (!SCIPfeof(file)) {
86+
/* get next line */
87+
if (SCIPfgets(buffer, (int)sizeof(buffer), file) == NULL) return SCIP_READERROR;
88+
lineno++;
89+
90+
/* parse dimension line */
91+
sprintf(format, "%%%ds\n", SCIP_MAXSTRLEN);
92+
nread = sscanf(buffer, format, name);
93+
if (nread == 0) {
94+
std::cerr << fmt::format("invalid input line {} in file <{}>: <{}>\n", lineno, filename, buffer);
95+
return SCIP_READERROR;
96+
}
97+
98+
std::cout << fmt::format("problem name <{}>\n", name);
99+
}
100+
101+
capacity = 0;
102+
n_items = 0;
103+
104+
/* read problem dimension */
105+
if (!SCIPfeof(file)) {
106+
/* get next line */
107+
if (SCIPfgets(buffer, (int)sizeof(buffer), file) == NULL) return SCIP_READERROR;
108+
lineno++;
109+
110+
/* parse dimension line */
111+
nread = sscanf(buffer, "%d %d %d\n", &capacity, &n_items, &bestsolvalue);
112+
if (nread < 2) {
113+
std::cerr << fmt::format("invalid input line {} in file <{}>: <{}>\n", lineno, filename, buffer);
114+
return SCIP_READERROR;
115+
}
116+
117+
std::cerr << fmt::format(
118+
"capacity = <{}>, number of items = <{}>, best known solution = <{}>\n", capacity, n_items, bestsolvalue);
119+
}
120+
121+
/* parse weights */
122+
weights.resize(n_items, 0);
123+
n_weights = 0;
124+
error = FALSE;
125+
126+
while (!SCIPfeof(file) && !error) {
127+
/* get next line */
128+
if (SCIPfgets(buffer, (int)sizeof(buffer), file) == NULL) break;
129+
lineno++;
130+
131+
/* parse the line */
132+
nread = sscanf(buffer, "%d\n", &weight);
133+
if (nread == 0) {
134+
std::cerr << fmt::format("invalid input line {} in file <{}>: <{}>\n", lineno, filename, buffer);
135+
error = TRUE;
136+
break;
137+
}
138+
139+
weights[n_weights] = weight;
140+
n_weights++;
141+
142+
if (n_weights == n_items) break;
143+
}
144+
145+
if (n_weights < n_items) {
146+
std::cerr << fmt::format(
147+
"set n_items from <{}> to <{}> since the file <{}> only contains <{}> weights\n",
148+
n_items,
149+
n_weights,
150+
filename,
151+
n_weights);
152+
n_items = n_weights;
153+
}
154+
155+
(void)SCIPfclose(file);
156+
157+
if (error) return SCIP_READERROR;
158+
return SCIP_OKAY;
159+
}
160+
161+
/** Create and add a single continuous variable the for the fraction of item weight (customer demand) served by the bin
162+
* (vehicle).
163+
*
164+
* Variables are automatically released (using the unique_ptr provided by scip::create_var_basic) after being captured
165+
* by the scip*. Their lifetime should not exceed that of the scip* (although that was already implied when creating
166+
* them).
167+
*/
168+
auto add_items_var(SCIP* scip, std::size_t i, std::size_t j, SCIP_Real cost, bool continuous) -> SCIP_VAR* {
169+
auto const name = fmt::format("x_{}_{}", i, j);
170+
auto unique_var = scip::create_var_basic(
171+
scip, name.c_str(), 0.0, 1.0, 0.0, /*add options for continuous variables */ SCIP_VARTYPE_BINARY);
172+
auto* var_ptr = unique_var.get();
173+
scip::call(SCIPaddVar, scip, var_ptr);
174+
return var_ptr;
175+
}
176+
177+
/** Create and add all variables for accumulated_weights the fraction of items weights (customer demands) from bins
178+
* (vehicles).
179+
*
180+
* Variables pointers are returned in a symmetric n_customers matrix .
181+
*/
182+
auto add_bins_items_vars(SCIP* scip, int n_bins, int n_items, xvector const& weights, bool continuous) {
183+
// symmetric matrix
184+
assert(weights.size() == n_items);
185+
186+
auto vars = xt::xtensor<SCIP_VAR*, 2>{{n_bins, n_items}, nullptr};
187+
for (std::size_t i = 0; i < n_bins; ++i) {
188+
for (std::size_t j = 0; j < n_items; ++j) {
189+
vars(i, j) = add_items_var(scip, i, j, weights[j], continuous);
190+
}
191+
}
192+
return vars;
193+
}
194+
195+
/** Create and add a single integer variable the representing the assignment of the item.
196+
*
197+
* Variables are automatically released (using the unique_ptr provided by scip::create_var_basic) after being captured
198+
* by the scip*. Their lifetime should not exceed that of the scip* (although that was already implied when creating
199+
* them).
200+
*/
201+
auto add_bins_var(SCIP* scip, std::size_t idx, double bin_cost) -> SCIP_VAR* {
202+
auto const name = fmt::format("y_{}", idx);
203+
auto unique_var = scip::create_var_basic(scip, name.c_str(), 0., 1., bin_cost, SCIP_VARTYPE_BINARY);
204+
auto* var_ptr = unique_var.get();
205+
scip::call(SCIPaddVar, scip, var_ptr);
206+
return var_ptr;
207+
}
208+
209+
auto add_bins_vars(SCIP* scip, std::size_t n_bins, double bin_cost) {
210+
auto vars = xt::xtensor<SCIP_VAR*, 1>({n_bins}, nullptr);
211+
auto* out_iter = vars.begin();
212+
for (std::size_t i = 0; i < n_bins; ++i) {
213+
*(out_iter++) = add_bins_var(scip, i, bin_cost);
214+
}
215+
return vars;
216+
}
217+
218+
/* capacity constraints */
219+
auto add_capacity_cons(
220+
SCIP* scip,
221+
xt::xtensor<SCIP_VAR*, 2> const& bins_items_vars,
222+
xt::xtensor<SCIP_VAR*, 1> const& bins_vars,
223+
xvector const& weights,
224+
int capacity) -> void {
225+
226+
auto const inf = SCIPinfinity(scip);
227+
228+
auto const [n_bins, n_items] = bins_items_vars.shape();
229+
230+
assert(weights.size() == n_items);
231+
232+
std::vector<value_type> coefs(weights.begin(), weights.end());
233+
coefs.push_back(-(SCIP_Real)capacity);
234+
235+
assert(coefs.size() == n_items + 1);
236+
237+
std::vector<std::size_t> shape = {n_items + 1};
238+
for (std::size_t i = 0; i < n_bins; ++i) {
239+
auto const name = fmt::format("c_{}", i);
240+
auto bins_items_vars_row = xt::row(bins_items_vars, i);
241+
std::vector<SCIP_VAR*> vars(bins_items_vars_row.begin(), bins_items_vars_row.end());
242+
vars.push_back(bins_vars(i));
243+
auto cons = scip::create_cons_basic_linear(scip, name.c_str(), vars.size(), vars.data(), coefs.data(), -inf, 0.);
244+
scip::call(SCIPaddCons, scip, cons.get());
245+
}
246+
}
247+
248+
// ensures that each item is packed only once (tightening)
249+
auto add_tightening_cons(SCIP* scip, xt::xtensor<SCIP_VAR*, 2> bins_items_vars) -> void {
250+
auto const inf = SCIPinfinity(scip);
251+
252+
auto const [n_bins, n_items] = bins_items_vars.shape();
253+
for (std::size_t i = 0; i < n_items; ++i) {
254+
auto name = fmt::format("tightening_cons_item_{}", i);
255+
auto const coefs = xvector({n_bins}, 1.);
256+
auto row = xt::col(bins_items_vars, i);
257+
std::vector<SCIP_VAR*> vars(row.begin(), row.end());
258+
auto cons = scip::create_cons_basic_linear(scip, name.c_str(), n_bins, vars.data(), coefs.data(), 1.0, 1.0);
259+
scip::call(SCIPaddCons, scip, cons.get());
260+
}
261+
}
262+
263+
} // namespace
264+
265+
scip::Model Binpacking::generate_instance(Binpacking::Parameters parameters, RandomGenerator& rng) {
266+
267+
double bin_cost = 1.0; // NOLINT(readability-magic-numbers)
268+
int capacity; // NOLINT(readability-magic-numbers)
269+
int n_items; // NOLINT(readability-magic-numbers)
270+
bool continuous_assignment = false; // NOLINT(readability-magic-numbers)
271+
std::vector<double> weights; // NOLINT(readability-magic-numbers)
272+
273+
if (!read_problem(parameters.filename, n_items, capacity, weights)) {
274+
throw SCIP_READERROR;
275+
}
276+
277+
auto xweights = static_cast<xvector>(xt::adapt(weights));
278+
279+
auto model = scip::Model::prob_basic();
280+
model.set_name(fmt::format("Binpacking-{}-{}", parameters.n_bins, n_items));
281+
282+
auto* const scip = model.get_scip_ptr();
283+
284+
auto const bins_vars = add_bins_vars(scip, parameters.n_bins, bin_cost);
285+
auto const bins_items_vars = add_bins_items_vars(scip, parameters.n_bins, n_items, xweights, continuous_assignment);
286+
287+
add_capacity_cons(scip, bins_items_vars, bins_vars, xweights, capacity);
288+
add_tightening_cons(scip, bins_items_vars);
289+
290+
return model;
291+
}
292+
293+
} // namespace ecole::instance

python/ecole/src/ecole/core/instance.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <pybind11/pybind11.h>
55

6+
#include "ecole/instance/bin-packing.hpp"
67
#include "ecole/instance/capacitated-facility-location.hpp"
78
#include "ecole/instance/capacitated-vehicle-routing.hpp"
89
#include "ecole/instance/combinatorial-auction.hpp"
@@ -365,6 +366,31 @@ void bind_submodule(py::module const& m) {
365366
def_iterator(capacitated_vehicle_routing_load);
366367
capacitated_vehicle_routing_load.def("seed", &CapacitatedVehicleRoutingLoader::seed, py::arg(" seed"));
367368

369+
// The Binpacking parameters used in constructor, generate_instance, and attributes
370+
auto constexpr binpacking_params = std::tuple{
371+
Member{"filename", &Binpacking::Parameters::filename},
372+
Member{"n_bins", &Binpacking::Parameters::n_bins},
373+
};
374+
// Bind Binpacking and remove intermediate Parameter class
375+
auto binpacking_load = py::class_<Binpacking>{m, "Binpacking"};
376+
def_generate_instance(binpacking_load, binpacking_params, R"(
377+
Load a Binpacking MILP problem instance.
378+
379+
The Bin-packing Problem (BPP) can be described, using the terminology of knapsack problems, as follows. Given $n$ items and $m$ knapsacks (or bins), with $w_j$ = weight of each item j, $c$ = capacity of each bin. Assign each item to one bin so that the total weight doesn't exceed its capacity and the number of bins used is minimum.
380+
381+
The same problem can be used to determine the number of minimum vehicles in Vehicle Routing Problem where bins represent vehicles and items represent customers demands.
382+
383+
Parameters
384+
----------
385+
filename:
386+
The Binpacking problem file.
387+
n_bins:
388+
The number of bins available.
389+
)");
390+
def_init(binpacking_load, binpacking_params);
391+
def_attributes(binpacking_load, binpacking_params);
392+
def_iterator(binpacking_load);
393+
binpacking_load.def("seed", &Binpacking::seed, py::arg(" seed"));
368394
}
369395

370396
/******************************************

0 commit comments

Comments
 (0)