Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
set_cover_solve.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include <algorithm>
15#include <cmath>
16#include <cstdint>
17#include <iostream>
18#include <string>
19#include <tuple>
20#include <vector>
21
22#include "absl/base/macros.h"
23#include "absl/flags/flag.h"
24#include "absl/log/check.h"
25#include "absl/log/log.h"
26#include "absl/strings/match.h"
27#include "absl/strings/str_cat.h"
28#include "absl/strings/str_format.h"
29#include "absl/strings/str_join.h"
30#include "absl/strings/string_view.h"
31#include "absl/time/time.h"
33#include "ortools/base/timer.h"
39
40// Example usages:
41//
42// Solve all the problems in the benchmarks directory and produce LaTeX output.
43// Run the classic algorithms on problem with up to 100,000 elements. Display
44// summaries (with geomean ratios) for each group of problems.
45/* Copy-paste to a terminal:
46 set_cover_solve --benchmarks --benchmarks_dir ~/benchmarks \
47 --max_elements_for_classic 100000 --solve --latex --summarize
48*/
49// Generate a new model from the rail4284 problem, with 100,000 elements and
50// 1,000,000,000 subsets, with row_scale = 1.1, column_scale = 1.1, and
51// cost_scale = 10.0:
52/* Copy-paste to a terminal:
53 set_cover_solve --input ~/scp-orlib/rail4284.txt --input_fmt rail \
54 --output ~/rail4284_1B.txt --output_fmt orlibrail \
55 --num_elements_wanted 100000 --num_subsets_wanted 100000000 \
56 --cost_scale 10.0 --row_scale 1.1 --column_scale 1.1 --generate
57*/
58// Display statistics about trail4284_1B.txt:
59/* Copy-paste to a terminal:
60 set_cover_solve --input ~/rail4284_1B.txt --input_fmt orlib --stats
61*/
62//
63
64ABSL_FLAG(std::string, input, "", "REQUIRED: Input file name.");
65ABSL_FLAG(std::string, input_fmt, "",
66 "REQUIRED: Input file format. Either proto, proto_bin, rail, "
67 "orlib or fimi.");
68
69ABSL_FLAG(std::string, output, "",
70 "If non-empty, write the returned solution to the given file.");
71ABSL_FLAG(std::string, output_fmt, "",
72 "If out is non-empty, use the given format for the output.");
73
74ABSL_FLAG(bool, generate, false, "Generate a new model from the input model.");
75ABSL_FLAG(int, num_elements_wanted, 0,
76 "Number of elements wanted in the new generated model.");
77ABSL_FLAG(int, num_subsets_wanted, 0,
78 "Number of subsets wanted in the new generated model.");
79ABSL_FLAG(float, row_scale, 1.0, "Row scale for the new generated model.");
80ABSL_FLAG(float, column_scale, 1.0,
81 "Column scale for the new generated model.");
82ABSL_FLAG(float, cost_scale, 1.0, "Cost scale for the new generated model.");
83
84ABSL_FLAG(bool, unicost, false, "Set all costs to 1.0.");
85
86ABSL_FLAG(bool, latex, false, "Output in LaTeX format. CSV otherwise.");
87
88ABSL_FLAG(bool, solve, false, "Solve the model.");
89ABSL_FLAG(bool, stats, false, "Log stats about the model.");
90ABSL_FLAG(bool, summarize, false,
91 "Display the comparison of the solution generators.");
92ABSL_FLAG(bool, tlns, false, "Run thrifty LNS.");
93ABSL_FLAG(int, max_elements_for_classic, 5000,
94 "Do not use classic on larger problems.");
95
96ABSL_FLAG(bool, benchmarks, false, "Run benchmarks.");
97ABSL_FLAG(std::string, benchmarks_dir, "", "Benchmarks directory.");
98ABSL_FLAG(bool, collate_scp, false, "Collate the SCP benchmarks.");
99
100// TODO(user): Add flags to:
101// - Choose problems by name or by size: filter_name, max_elements, max_subsets.
102// - Exclude problems by name: exclude_name.
103// - Choose which solution generators to run.
104// - Parameterize the number of threads. num_threads.
105
106namespace operations_research {
108
110 return absl::ToInt64Microseconds(gen.run_time());
111}
112
114 return absl::ToInt64Nanoseconds(gen.run_time());
115}
116
117void LogStats(const SetCoverModel& model) {
118 const std::string sep = absl::GetFlag(FLAGS_latex) ? " & " : ", ";
119 std::string header = absl::StrCat(model.name(), sep);
120 // Lines start with a comma to make it easy to copy-paste the output to a
121 // spreadsheet as CSV.
122 if (!absl::GetFlag(FLAGS_latex)) {
123 header = absl::StrCat(sep, header);
124 }
125 LOG(INFO) << header << model.ToVerboseString(sep);
126 LOG(INFO) << header << "cost" << sep
127 << model.ComputeCostStats().ToVerboseString(sep);
128 LOG(INFO) << header << "row size stats" << sep
129 << model.ComputeRowStats().ToVerboseString(sep);
130 LOG(INFO) << header << "row size deciles" << sep
131 << absl::StrJoin(model.ComputeRowDeciles(), sep);
132 LOG(INFO) << header << "column size stats" << sep
133 << model.ComputeColumnStats().ToVerboseString(sep);
134 LOG(INFO) << header << "column size deciles" << sep
135 << absl::StrJoin(model.ComputeColumnDeciles(), sep);
136 LOG(INFO) << header << "num_singleton_rows" << sep
137 << model.ComputeNumSingletonRows() << sep << "num_singleton_columns"
138 << sep << model.ComputeNumSingletonColumns();
139}
140
141void LogCostAndTiming(const absl::string_view problem_name,
142 absl::string_view alg_name, const SetCoverInvariant& inv,
143 int64_t run_time) {
144 LOG(INFO) << ", " << problem_name << ", " << alg_name << ", cost, "
145 << inv.CostOrLowerBound() << ", solution_cardinality, "
146 << inv.ComputeCardinality() << ", " << run_time << "e-6, s";
147}
148
150 const SetCoverInvariant& inv = *generator.inv();
151 const SetCoverModel& model = *inv.model();
152 LogCostAndTiming(model.name(), generator.name(), inv,
153 RunTimeInMicroseconds(generator));
154}
155
156// TODO(user): Move this set_cover_reader
166
167// TODO(user): Move this set_cover_reader
168FileFormat ParseFileFormat(const std::string& format_name) {
169 if (format_name.empty()) {
170 return FileFormat::EMPTY;
171 } else if (absl::EqualsIgnoreCase(format_name, "orlib")) {
172 return FileFormat::ORLIB;
173 } else if (absl::EqualsIgnoreCase(format_name, "rail")) {
174 return FileFormat::RAIL;
175 } else if (absl::EqualsIgnoreCase(format_name, "fimi")) {
176 return FileFormat::FIMI;
177 } else if (absl::EqualsIgnoreCase(format_name, "proto")) {
178 return FileFormat::PROTO;
179 } else if (absl::EqualsIgnoreCase(format_name, "proto_bin")) {
181 } else if (absl::EqualsIgnoreCase(format_name, "txt")) {
182 return FileFormat::TXT;
183 } else {
184 LOG(FATAL) << "Unsupported input format: " << format_name;
185 }
186}
187
188// TODO(user): Move this set_cover_reader
189SetCoverModel ReadModel(absl::string_view filename, FileFormat format) {
190 switch (format) {
192 return ReadOrlibScp(filename);
193 case FileFormat::RAIL:
194 return ReadOrlibRail(filename);
195 case FileFormat::FIMI:
196 return ReadFimiDat(filename);
198 return ReadSetCoverProto(filename, /*binary=*/false);
200 return ReadSetCoverProto(filename, /*binary=*/true);
201 default:
202 LOG(FATAL) << "Unsupported input format: " << static_cast<int>(format);
203 }
204}
205
206// TODO(user): Move this set_cover_reader
207SubsetBoolVector ReadSolution(absl::string_view filename, FileFormat format) {
208 switch (format) {
209 case FileFormat::TXT:
210 return ReadSetCoverSolutionText(filename);
212 return ReadSetCoverSolutionProto(filename, /*binary=*/false);
214 return ReadSetCoverSolutionProto(filename, /*binary=*/true);
215 default:
216 LOG(FATAL) << "Unsupported input format: " << static_cast<int>(format);
217 }
218}
219
220// TODO(user): Move this set_cover_reader
221void WriteModel(const SetCoverModel& model, const std::string& filename,
222 FileFormat format) {
223 LOG(INFO) << "Writing model to " << filename;
224 switch (format) {
226 WriteOrlibScp(model, filename);
227 break;
228 case FileFormat::RAIL:
229 WriteOrlibRail(model, filename);
230 break;
232 WriteSetCoverProto(model, filename, /*binary=*/false);
233 break;
235 WriteSetCoverProto(model, filename, /*binary=*/true);
236 break;
237 default:
238 LOG(FATAL) << "Unsupported output format: " << static_cast<int>(format);
239 }
240}
241
242// TODO(user): Move this set_cover_reader
244 absl::string_view filename, FileFormat format) {
245 switch (format) {
246 case FileFormat::TXT:
247 WriteSetCoverSolutionText(model, solution, filename);
248 break;
250 WriteSetCoverSolutionProto(model, solution, filename,
251 /*binary=*/false);
252 break;
254 WriteSetCoverSolutionProto(model, solution, filename,
255 /*binary=*/true);
256 break;
257 default:
258 LOG(FATAL) << "Unsupported output format: " << static_cast<int>(format);
259 }
260}
261
263 SetCoverInvariant inv(model);
264 LazyElementDegreeSolutionGenerator element_degree(&inv);
265 CHECK(element_degree.NextSolution());
266 DCHECK(inv.CheckConsistency(CL::kCostAndCoverage));
267 LogCostAndTiming(element_degree);
268 return inv;
269}
270
272 SetCoverInvariant inv(model);
273 GreedySolutionGenerator classic(&inv);
274 CHECK(classic.NextSolution());
275 DCHECK(inv.CheckConsistency(CL::kCostAndCoverage));
276 LogCostAndTiming(classic);
277 return inv;
278}
279
280// Formatting and reporting functions with LaTeX and CSV support.
281namespace {
282std::string Separator() { return absl::GetFlag(FLAGS_latex) ? " & " : ", "; }
283
284std::string Eol() { return absl::GetFlag(FLAGS_latex) ? " \\\\\n" : "\n"; }
285
286// Computes the ratio of the cost of the new solution generator to the cost of
287// the reference solution generator.
288double CostRatio(SetCoverSolutionGenerator& ref_gen,
289 SetCoverSolutionGenerator& new_gen) {
290 return new_gen.cost() / ref_gen.cost();
291}
292
293// Computes the speedup of the new solution generator compared to the reference
294// solution generator, where the speedup is defined as the ratio of the
295// cumulated time of the reference solution generator to the cumulated time of
296// the new solution generator.
297double Speedup(SetCoverSolutionGenerator& ref_gen,
298 SetCoverSolutionGenerator& new_gen) {
299 // Avoid division by zero by considering the case where the new generator took
300 // less than 1 nanosecond (!) to run.
301 return 1.0 * RunTimeInNanoseconds(ref_gen) /
302 std::max<int64_t>(1, RunTimeInNanoseconds(new_gen));
303}
304
305// Same as above, but the cumulated time is the sum of the initialization and
306// search times for two pairs of solution generators.
307double SpeedupOnCumulatedTimes(SetCoverSolutionGenerator& ref_init,
308 SetCoverSolutionGenerator& ref_search,
310 SetCoverSolutionGenerator& new_search) {
311 return 1.0 *
312 (RunTimeInNanoseconds(ref_init) + RunTimeInNanoseconds(ref_search)) /
313 std::max<int64_t>(1, RunTimeInNanoseconds(new_init) +
314 RunTimeInNanoseconds(new_search));
315}
316
317// In the case of LaTeX, the stats are printed in the format:
318// & 123.456 (123) +/- 12.34 (12) & [123, 456] corresponding to
319// & mean (median) +/- stddev (iqr) & [min, max]
320// In the case of CSV, the stats are printed as a VerboseString.
321std::string FormatStats(const SetCoverModel::Stats& stats) {
322 return absl::GetFlag(FLAGS_latex)
323 ? absl::StrFormat(
324 " & $%#.2f (%.0f) \\pm %#.2f (%.0f)$ & $[%.0f, %.0f]$",
325 stats.mean, stats.median, stats.stddev, stats.iqr, stats.min,
326 stats.max)
327 : stats.ToVerboseString(", ");
328}
329
330// Returns the string str in LaTeX bold if condition is true and --latex is
331// true.
332std::string BoldIf(bool condition, absl::string_view str) {
333 return condition && absl::GetFlag(FLAGS_latex)
334 ? absl::StrCat("\\textbf{", str, "}")
335 : std::string(str);
336}
337
338// Formats time in microseconds for LaTeX. For CSV, it is formatted as
339// seconds by adding the suffix "e-6, s".
340std::string FormatTime(int64_t time_us) {
341 return absl::GetFlag(FLAGS_latex) ? absl::StrFormat("%d", time_us)
342 : absl::StrFormat("%de-6, s", time_us);
343}
344
345// Formats the cost and time, with cost in bold if the condition is true.
346std::string FormatCostAndTimeIf(bool condition, double cost, int64_t time_us) {
347 const std::string cost_display =
348 BoldIf(condition, absl::StrFormat("%.9g", cost));
349 return absl::StrCat(cost_display, Separator(), FormatTime(time_us));
350}
351
352// Formats the speedup with one decimal place.
353// TODO(user): Bolden the speedup if it is better than 1.0.
354std::string FormattedSpeedup(SetCoverSolutionGenerator& ref_gen,
355 SetCoverSolutionGenerator& new_gen) {
356 return absl::StrFormat("%.1f", Speedup(ref_gen, new_gen));
357}
358
359// Reports the second of the comparison of two solution generators, with only
360// the cost and time of the new solution generator.
361std::string ReportSecondPart(SetCoverSolutionGenerator& ref_gen,
362 SetCoverSolutionGenerator& new_gen) {
363 const double ref_cost = ref_gen.cost();
364 const double new_cost = new_gen.cost();
365 return absl::StrCat(Separator(),
366 FormatCostAndTimeIf(new_cost <= ref_cost, new_cost,
367 RunTimeInMicroseconds(new_gen)),
368 Separator(), FormattedSpeedup(ref_gen, new_gen));
369}
370
371// Reports the cost and time of both solution generators.
372std::string ReportBothParts(SetCoverSolutionGenerator& ref_gen,
373 SetCoverSolutionGenerator& new_gen) {
374 const double ref_cost = ref_gen.cost();
375 const double new_cost = new_gen.cost();
376 return absl::StrCat(Separator(),
377 FormatCostAndTimeIf(ref_cost <= new_cost, ref_cost,
378 RunTimeInMicroseconds(ref_gen)),
379 ReportSecondPart(ref_gen, new_gen));
380}
381
382// Formats the model and stats in LaTeX or CSV format.
383std::string FormatModelAndStats(const SetCoverModel& model) {
384 if (absl::GetFlag(FLAGS_latex)) {
385 return absl::StrCat(model.name(), Separator(), model.ToString(Separator()),
386 FormatStats(model.ComputeColumnStats()),
387 FormatStats(model.ComputeRowStats()));
388 } else { // CSV
389 const std::string header =
390 absl::StrCat(Separator(), model.name(), Separator());
391 return absl::StrCat(header, model.ToString(Separator()), Eol(), header,
392 "column size stats", Separator(),
393 FormatStats(model.ComputeColumnStats()), Eol(), header,
394 "row size stats", Separator(),
395 FormatStats(model.ComputeRowStats()), Eol());
396 }
397}
398
399// Sets the name of the model to the filename, with a * suffix if the model is
400// unicost. Removes the .txt suffix from the filename if any.
401void SetModelName(absl::string_view filename, SetCoverModel* model) {
402 std::string problem_name = std::string(filename);
403 constexpr absl::string_view kTxtSuffix = ".txt";
404 // Remove the .txt suffix from the problem name if any.
405 if (absl::EndsWith(problem_name, kTxtSuffix)) {
406 problem_name.resize(problem_name.size() - kTxtSuffix.size());
407 }
408 if (absl::GetFlag(FLAGS_unicost) || model->is_unicost()) {
409 absl::StrAppend(&problem_name, "*");
410 }
411 model->SetName(problem_name);
412}
413
414// Accumulates data to compute the geometric mean of a sequence of values.
415class GeometricMean {
416 public:
417 GeometricMean() : sum_(0.0), count_(0) {}
418 void Add(double value) {
419 sum_ += std::log(value);
420 ++count_;
421 }
422 double Get() const { return std::exp(sum_ / count_); }
423
424 private:
425 double sum_;
426 int count_;
427};
428
429std::vector<std::string> BuildVector(const char* const files[], int size) {
430 return std::vector<std::string>(files, files + size);
431}
432} // namespace
433
434// List all the files from the literature.
435static const char* const kRailFiles[] = {
436 "rail507.txt", "rail516.txt", "rail582.txt", "rail2536.txt",
437 "rail2586.txt", "rail4284.txt", "rail4872.txt",
438};
439
440static const char* const kScp4To6Files[] = {
441 "scp41.txt", "scp42.txt", "scp43.txt", "scp44.txt", "scp45.txt",
442 "scp46.txt", "scp47.txt", "scp48.txt", "scp49.txt", "scp410.txt",
443 "scp51.txt", "scp52.txt", "scp53.txt", "scp54.txt", "scp55.txt",
444 "scp56.txt", "scp57.txt", "scp58.txt", "scp59.txt", "scp510.txt",
445 "scp61.txt", "scp62.txt", "scp63.txt", "scp64.txt", "scp65.txt",
446};
447
448static const char* const kScpAToEFiles[] = {
449 "scpa1.txt", "scpa2.txt", "scpa3.txt", "scpa4.txt", "scpa5.txt",
450 "scpb1.txt", "scpb2.txt", "scpb3.txt", "scpb4.txt", "scpb5.txt",
451 "scpc1.txt", "scpc2.txt", "scpc3.txt", "scpc4.txt", "scpc5.txt",
452 "scpd1.txt", "scpd2.txt", "scpd3.txt", "scpd4.txt", "scpd5.txt",
453 "scpe1.txt", "scpe2.txt", "scpe3.txt", "scpe4.txt", "scpe5.txt",
454};
455
456static const char* const kScpNrFiles[] = {
457 "scpnre1.txt", "scpnre2.txt", "scpnre3.txt", "scpnre4.txt", "scpnre5.txt",
458 "scpnrf1.txt", "scpnrf2.txt", "scpnrf3.txt", "scpnrf4.txt", "scpnrf5.txt",
459 "scpnrg1.txt", "scpnrg2.txt", "scpnrg3.txt", "scpnrg4.txt", "scpnrg5.txt",
460 "scpnrh1.txt", "scpnrh2.txt", "scpnrh3.txt", "scpnrh4.txt", "scpnrh5.txt",
461};
462
463static const char* const kScpClrFiles[] = {
464 "scpclr10.txt",
465 "scpclr11.txt",
466 "scpclr12.txt",
467 "scpclr13.txt",
468};
469
470static const char* const kScpCycFiles[] = {
471 "scpcyc06.txt", "scpcyc07.txt", "scpcyc08.txt",
472 "scpcyc09.txt", "scpcyc10.txt", "scpcyc11.txt",
473};
474
475static const char* const kWedelinFiles[] = {
476 "a320_coc.txt", "a320.txt", "alitalia.txt",
477 "b727.txt", "sasd9imp2.txt", "sasjump.txt",
478};
479
480static const char* const kBalasFiles[] = {
481 "aa03.txt", "aa04.txt", "aa05.txt", "aa06.txt", "aa11.txt", "aa12.txt",
482 "aa13.txt", "aa14.txt", "aa15.txt", "aa16.txt", "aa17.txt", "aa18.txt",
483 "aa19.txt", "aa20.txt", "bus1.txt", "bus2.txt",
484};
485
486static const char* const kFimiFiles[] = {
487 "accidents.dat", "chess.dat", "connect.dat", "kosarak.dat",
488 "mushroom.dat", // "pumsb.dat", "pumsb_star.dat",
489 "retail.dat", "webdocs.dat",
490};
491
493 std::tuple<std::string, std::vector<std::string>, FileFormat>;
494
495std::vector<BenchmarksTableRow> BenchmarksTable() {
496// This creates a vector of tuples, where each tuple contains the directory
497// name, the vector of files and the file format.
498// It is assumed that the scp* files are in BENCHMARKS_DIR/scp-orlib, the rail
499// files are in BENCHMARKS_DIR/scp-rail, etc., with BENCHMARKS_DIR being the
500// directory specified by the --benchmarks_dir flag.
501// Use a macro to be able to compute the size of the array at compile time.
502#define BUILD_VECTOR(files) BuildVector(files, ABSL_ARRAYSIZE(files))
503 std::vector<BenchmarksTableRow> result;
504 std::vector<std::string> all_scp_files;
505 if (absl::GetFlag(FLAGS_collate_scp)) {
506 all_scp_files = BUILD_VECTOR(kScp4To6Files);
507 all_scp_files.insert(all_scp_files.end(), kScpAToEFiles,
508 kScpAToEFiles + ABSL_ARRAYSIZE(kScpAToEFiles));
509 all_scp_files.insert(all_scp_files.end(), kScpNrFiles,
510 kScpNrFiles + ABSL_ARRAYSIZE(kScpNrFiles));
511 all_scp_files.insert(all_scp_files.end(), kScpCycFiles,
512 kScpCycFiles + ABSL_ARRAYSIZE(kScpCycFiles));
513 result = {{"scp-orlib", all_scp_files, FileFormat::ORLIB}};
514 } else {
515 result = {
521 };
522 }
523 result.insert(
524 result.end(),
525 {
526 {"scp-rail", BUILD_VECTOR(kRailFiles), FileFormat::RAIL},
527 {"scp-wedelin", BUILD_VECTOR(kWedelinFiles), FileFormat::ORLIB},
528 {"scp-balas", BUILD_VECTOR(kBalasFiles), FileFormat::ORLIB},
529 {"scp-fimi", BUILD_VECTOR(kFimiFiles), FileFormat::FIMI},
530 });
531 return result;
532
533#undef BUILD_VECTOR
534}
535
537 QCHECK(!absl::GetFlag(FLAGS_benchmarks_dir).empty())
538 << "Benchmarks directory must be specified.";
539 const std::vector<BenchmarksTableRow> kBenchmarks = BenchmarksTable();
540 const std::string kSep = Separator();
541 const std::string kEol = Eol();
542 if (absl::GetFlag(FLAGS_stats)) {
543 std::cout << absl::StrJoin(std::make_tuple("Problem", "|S|", "|U|", "nnz",
544 "Fill", "Col size", "Row size"),
545 kSep)
546 << kEol;
547 }
548 for (const auto& [dir, files, input_format] : kBenchmarks) {
549 GeometricMean element_vs_classic_cost_ratio_geomean;
550 GeometricMean element_vs_classic_speedup_geomean;
551 GeometricMean lazy_vs_classic_cost_ratio_geomean;
552 GeometricMean lazy_vs_classic_speedup_geomean;
553 GeometricMean lazy_steepest_vs_steepest_cost_ratio_geomean;
554 GeometricMean lazy_steepest_vs_steepest_speedup_geomean;
555 GeometricMean combined_cost_ratio_geomean;
556 GeometricMean combined_speedup_geomean;
557 GeometricMean tlns_cost_ratio_geomean;
558 for (const std::string& filename : files) {
559 const std::string filespec = absl::StrCat(
560 absl::GetFlag(FLAGS_benchmarks_dir), "/", dir, "/", filename);
561
562 LOG(INFO) << "Reading " << filespec;
563 SetCoverModel model = ReadModel(filespec, input_format);
564 if (absl::GetFlag(FLAGS_unicost)) {
565 for (const SubsetIndex subset : model.SubsetRange()) {
566 model.SetSubsetCost(subset, 1.0);
567 }
568 }
569 SetModelName(filename, &model);
570 if (absl::GetFlag(FLAGS_stats)) {
571 std::cout << FormatModelAndStats(model) << kEol;
572 }
573 if (!absl::GetFlag(FLAGS_solve)) continue;
574 LOG(INFO) << "Solving " << model.name();
575 std::string output =
576 absl::StrJoin(std::make_tuple(model.name(), model.num_subsets(),
577 model.num_elements()),
578 kSep);
579 model.CreateSparseRowView();
580
581 SetCoverInvariant classic_inv(&model);
582 GreedySolutionGenerator classic(&classic_inv);
583 SteepestSearch steepest(&classic_inv);
584 const bool run_classic_solvers =
585 model.num_elements() <= absl::GetFlag(FLAGS_max_elements_for_classic);
586 if (run_classic_solvers) {
587 CHECK(classic.NextSolution());
588 CHECK(steepest.NextSolution());
589 DCHECK(classic_inv.CheckConsistency(CL::kCostAndCoverage));
590 }
591
592 SetCoverInvariant element_degree_inv(&model);
593 ElementDegreeSolutionGenerator element_degree(&element_degree_inv);
594 CHECK(element_degree.NextSolution());
595 DCHECK(element_degree_inv.CheckConsistency(CL::kCostAndCoverage));
596
597 absl::StrAppend(&output, ReportBothParts(classic, element_degree));
598
599 SetCoverInvariant lazy_inv(&model);
600 LazyElementDegreeSolutionGenerator lazy_element_degree(&lazy_inv);
601 CHECK(lazy_element_degree.NextSolution());
602 DCHECK(lazy_inv.CheckConsistency(CL::kCostAndCoverage));
603
604 absl::StrAppend(&output, ReportBothParts(classic, lazy_element_degree));
605
606 LazySteepestSearch lazy_steepest(&lazy_inv);
607 lazy_steepest.NextSolution();
608 absl::StrAppend(&output, ReportBothParts(steepest, lazy_steepest));
609
610 if (run_classic_solvers) {
611 element_vs_classic_cost_ratio_geomean.Add(
612 CostRatio(classic, element_degree));
613 element_vs_classic_speedup_geomean.Add(
614 Speedup(classic, element_degree));
615 lazy_vs_classic_cost_ratio_geomean.Add(
616 CostRatio(classic, lazy_element_degree));
617 lazy_vs_classic_speedup_geomean.Add(
618 Speedup(classic, lazy_element_degree));
619 lazy_steepest_vs_steepest_cost_ratio_geomean.Add(
620 CostRatio(steepest, lazy_steepest));
621 lazy_steepest_vs_steepest_speedup_geomean.Add(
622 Speedup(steepest, lazy_steepest));
623 combined_cost_ratio_geomean.Add(CostRatio(classic, lazy_steepest));
624 combined_speedup_geomean.Add(SpeedupOnCumulatedTimes(
625 classic, steepest, lazy_element_degree, lazy_steepest));
626 }
627
628 Cost best_cost = lazy_inv.cost();
629 SubsetBoolVector best_assignment = lazy_inv.is_selected();
630 if (absl::GetFlag(FLAGS_tlns)) {
631 WallTimer timer;
632 LazySteepestSearch steepest(&lazy_inv);
633 timer.Start();
634 for (int i = 0; i < 500; ++i) {
635 std::vector<SubsetIndex> range =
636 ClearRandomSubsets(0.1 * lazy_inv.trace().size(), &lazy_inv);
637 CHECK(lazy_element_degree.NextSolution());
638 CHECK(steepest.NextSolution());
639 if (lazy_inv.cost() < best_cost) {
640 best_cost = lazy_inv.cost();
641 best_assignment = lazy_inv.is_selected();
642 }
643 }
644 timer.Stop();
645 absl::StrAppend(
646 &output, kSep,
647 FormatCostAndTimeIf(
648 best_cost <= lazy_element_degree.cost() &&
649 best_cost <= classic.cost(),
650 best_cost, absl::ToInt64Microseconds(timer.GetDuration())));
651 tlns_cost_ratio_geomean.Add(best_cost / classic.cost());
652 }
653 std::cout << output << kEol;
654 }
655
656 if (absl::GetFlag(FLAGS_summarize)) {
657 std::cout
658 << "Element degree / classic speedup geometric mean: "
659 << element_vs_classic_speedup_geomean.Get() << kEol
660 << "Element degree / classic cost ratio geometric mean: "
661 << element_vs_classic_cost_ratio_geomean.Get() << kEol
662 << "Lazy element degree / classic speedup geometric mean: "
663 << lazy_vs_classic_speedup_geomean.Get() << kEol
664 << "Lazy element degree / classic cost ratio geometric mean: "
665 << lazy_vs_classic_cost_ratio_geomean.Get() << kEol
666 << "Improvement element degree / classic speedup geometric mean: "
667 << lazy_steepest_vs_steepest_speedup_geomean.Get() << kEol
668 << "Improvement cost ratio geometric mean: "
669 << lazy_steepest_vs_steepest_cost_ratio_geomean.Get() << kEol
670 << "Combined speedup geometric mean: "
671 << combined_speedup_geomean.Get() << kEol;
672 if (absl::GetFlag(FLAGS_tlns)) {
673 std::cout << "TLNS cost ratio geometric mean: "
674 << tlns_cost_ratio_geomean.Get() << kEol;
675 }
676 }
677 }
678}
679
680void Run() {
681 const auto& input = absl::GetFlag(FLAGS_input);
682 const auto& input_format = ParseFileFormat(absl::GetFlag(FLAGS_input_fmt));
683 const auto& output = absl::GetFlag(FLAGS_output);
684 const auto& output_format = ParseFileFormat(absl::GetFlag(FLAGS_output_fmt));
685 QCHECK(!input.empty()) << "No input file specified.";
686 QCHECK(input.empty() || input_format != FileFormat::EMPTY)
687 << "Input format cannot be empty.";
688 QCHECK(output.empty() || output_format != FileFormat::EMPTY)
689 << "Output format cannot be empty.";
690 SetCoverModel model = ReadModel(input, input_format);
691 if (absl::GetFlag(FLAGS_generate)) {
692 model.CreateSparseRowView();
694 model, absl::GetFlag(FLAGS_num_elements_wanted),
695 absl::GetFlag(FLAGS_num_subsets_wanted), absl::GetFlag(FLAGS_row_scale),
696 absl::GetFlag(FLAGS_column_scale), absl::GetFlag(FLAGS_cost_scale));
697 }
698 if (!output.empty()) {
699 if (output_format == FileFormat::ORLIB) {
700 model.CreateSparseRowView();
701 }
702 WriteModel(model, output, output_format);
703 }
704 auto problem = output.empty() ? input : output;
705 if (absl::GetFlag(FLAGS_stats)) {
706 LogStats(model);
707 }
708 if (absl::GetFlag(FLAGS_solve)) {
709 LOG(INFO) << "Solving " << problem;
710 model.CreateSparseRowView();
712 }
713}
714} // namespace operations_research
715
716int main(int argc, char** argv) {
717 InitGoogle(argv[0], &argc, &argv, true);
718 if (absl::GetFlag(FLAGS_benchmarks)) {
720 } else {
722 }
723 return 0;
724}
absl::Duration GetDuration() const
Definition timer.h:47
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:30
void Stop()
Definition timer.h:38
The consistency level is maintained up to kFreeAndUncovered.
bool NextSolution(const SubsetBoolVector &in_focus) final
The consistency level is maintained up to kFreeAndUncovered.
bool NextSolution(absl::Span< const SubsetIndex > focus) final
GreedySolutionGenerator.
bool NextSolution(const SubsetBoolVector &in_focus) final
bool NextSolution(const SubsetBoolVector &in_focus) final
LazySteepestSearch.
BaseInt ComputeCardinality() const
Returns the cardinality of the solution in the invariant.
const std::vector< SetCoverDecision > & trace() const
Returns the vector of the decisions which have led to the current solution.
bool CheckConsistency(ConsistencyLevel consistency) const
Checks the consistency of the invariant at the given consistency level.
const SubsetBoolVector & is_selected() const
Returns the subset assignment vector.
SetCoverModel * model() const
Returns the weighted set covering model to which the state applies.
Cost cost() const
Returns the cost of current solution.
Main class for describing a weighted set-covering problem.
Stats ComputeCostStats() const
Computes basic statistics on costs and returns a Stats structure.
static SetCoverModel GenerateRandomModelFrom(const SetCoverModel &seed_model, BaseInt num_elements, BaseInt num_subsets, double row_scale, double column_scale, double cost_scale)
std::vector< int64_t > ComputeRowDeciles() const
Computes deciles on rows and returns a vector of deciles.
Stats ComputeRowStats() const
Computes basic statistics on rows and returns a Stats structure.
util_intops::StrongIntRange< SubsetIndex > SubsetRange() const
Access to the ranges of subsets and elements.
std::string ToVerboseString(absl::string_view sep) const
void SetSubsetCost(BaseInt subset, Cost cost)
Stats ComputeColumnStats() const
Computes basic statistics on columns and returns a Stats structure.
std::vector< int64_t > ComputeColumnDeciles() const
Computes deciles on columns and returns a vector of deciles.
std::string name() const
Returns the name of the heuristic.
Cost cost() const
Returns the current cost of the solution in the invariant.
The consistency level is maintained up to kFreeAndUncovered.
bool NextSolution(const SubsetBoolVector &in_focus) final
SteepestSearch.
absl::StatusOr< std::pair< ModelProto, std::optional< SolutionHintProto > > > ReadModel(absl::string_view file_path, FileFormat format)
absl::Status WriteModel(absl::string_view file_path, const ModelProto &model_proto, const std::optional< SolutionHintProto > &hint_proto, FileFormat format)
void InitGoogle(absl::string_view usage, int *argc, char ***argv, bool deprecated)
Definition init_google.h:49
In SWIG mode, we don't want anything besides these top-level includes.
SetCoverInvariant RunLazyElementDegree(SetCoverModel *model)
SetCoverModel ReadOrlibScp(absl::string_view filename)
This is a row-based format where the elements are 1-indexed.
void WriteSetCoverProto(const SetCoverModel &model, absl::string_view filename, bool binary)
SetCoverModel ReadSetCoverProto(absl::string_view filename, bool binary)
void WriteSetCoverSolutionProto(const SetCoverModel &model, const SubsetBoolVector &solution, absl::string_view filename, bool binary)
static const char *const kScpAToEFiles[]
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
SetCoverInvariant::ConsistencyLevel CL
std::vector< BenchmarksTableRow > BenchmarksTable()
std::vector< SubsetIndex > ClearRandomSubsets(BaseInt num_subsets, SetCoverInvariant *inv)
The consistency level is maintained up to kCostAndCoverage.
SubsetBoolVector ReadSolution(absl::string_view filename, FileFormat format)
int64_t RunTimeInMicroseconds(const SetCoverSolutionGenerator &gen)
static const char *const kBalasFiles[]
static const char *const kScpCycFiles[]
SubsetBoolVector ReadSetCoverSolutionText(absl::string_view filename)
void WriteModel(const SetCoverModel &model, const std::string &filename, FileFormat format)
int64_t RunTimeInNanoseconds(const SetCoverSolutionGenerator &gen)
static const char *const kRailFiles[]
List all the files from the literature.
void LogStats(const SetCoverModel &model)
void LogCostAndTiming(const absl::string_view problem_name, absl::string_view alg_name, const SetCoverInvariant &inv, int64_t run_time)
void WriteSetCoverSolutionText(const SetCoverModel &model, const SubsetBoolVector &solution, absl::string_view filename)
std::tuple< std::string, std::vector< std::string >, FileFormat > BenchmarksTableRow
void WriteSolution(const SetCoverModel &model, const SubsetBoolVector &solution, absl::string_view filename, FileFormat format)
SetCoverInvariant RunGreedy(SetCoverModel *model)
FileFormat ParseFileFormat(const std::string &format_name)
SetCoverModel ReadOrlibRail(absl::string_view filename)
This is a column-based format where the elements are 1-indexed.
util_intops::StrongVector< SubsetIndex, bool > SubsetBoolVector
Definition base_types.h:71
static const char *const kScpNrFiles[]
static const char *const kScp4To6Files[]
static const char *const kWedelinFiles[]
void WriteOrlibScp(const SetCoverModel &model, absl::string_view filename)
SetCoverModel ReadModel(absl::string_view filename, FileFormat format)
void WriteOrlibRail(const SetCoverModel &model, absl::string_view filename)
Beware the fact that elements written are converted to 1-indexed.
SubsetBoolVector ReadSetCoverSolutionProto(absl::string_view filename, bool binary)
double Cost
Basic non-strict type for cost. The speed penalty for using double is ~2%.
Definition base_types.h:32
SetCoverModel ReadFimiDat(absl::string_view filename)
static const char *const kScpClrFiles[]
static const char *const kFimiFiles[]
static int input(yyscan_t yyscanner)
#define BUILD_VECTOR(files)
int main(int argc, char **argv)
ABSL_FLAG(std::string, input, "", "REQUIRED: Input file name.")
Display statistics about trail4284_1B.txt:
std::string ToVerboseString(absl::string_view sep) const