|
| 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 |
0 commit comments