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"
66 "REQUIRED: Input file format. Either proto, proto_bin, rail, "
70 "If non-empty, write the returned solution to the given file.");
72 "If out is non-empty, use the given format for the output.");
74ABSL_FLAG(
bool, generate,
false,
"Generate a new model from the input model.");
76 "Number of elements wanted in the new generated model.");
78 "Number of subsets wanted in the new generated model.");
79ABSL_FLAG(
float, row_scale, 1.0,
"Row scale for the new generated model.");
81 "Column scale for the new generated model.");
82ABSL_FLAG(
float, cost_scale, 1.0,
"Cost scale for the new generated model.");
84ABSL_FLAG(
bool, unicost,
false,
"Set all costs to 1.0.");
86ABSL_FLAG(
bool, latex,
false,
"Output in LaTeX format. CSV otherwise.");
89ABSL_FLAG(
bool, stats,
false,
"Log stats about the model.");
91 "Display the comparison of the solution generators.");
94 "Do not use classic on larger problems.");
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.");
110 return absl::ToInt64Microseconds(gen.
run_time());
114 return absl::ToInt64Nanoseconds(gen.
run_time());
118 const std::string sep = absl::GetFlag(FLAGS_latex) ?
" & " :
", ";
119 std::string header = absl::StrCat(model.
name(), sep);
122 if (!absl::GetFlag(FLAGS_latex)) {
123 header = absl::StrCat(sep, header);
126 LOG(INFO) << header <<
"cost" << sep
128 LOG(INFO) << header <<
"row size stats" << sep
130 LOG(INFO) << header <<
"row size deciles" << sep
132 LOG(INFO) << header <<
"column size stats" << sep
134 LOG(INFO) << header <<
"column size deciles" << sep
136 LOG(INFO) << header <<
"num_singleton_rows" << sep
144 LOG(INFO) <<
", " << problem_name <<
", " << alg_name <<
", cost, "
169 if (format_name.empty()) {
171 }
else if (absl::EqualsIgnoreCase(format_name,
"orlib")) {
173 }
else if (absl::EqualsIgnoreCase(format_name,
"rail")) {
175 }
else if (absl::EqualsIgnoreCase(format_name,
"fimi")) {
177 }
else if (absl::EqualsIgnoreCase(format_name,
"proto")) {
179 }
else if (absl::EqualsIgnoreCase(format_name,
"proto_bin")) {
181 }
else if (absl::EqualsIgnoreCase(format_name,
"txt")) {
184 LOG(FATAL) <<
"Unsupported input format: " << format_name;
202 LOG(FATAL) <<
"Unsupported input format: " <<
static_cast<int>(format);
216 LOG(FATAL) <<
"Unsupported input format: " <<
static_cast<int>(format);
223 LOG(INFO) <<
"Writing model to " << filename;
238 LOG(FATAL) <<
"Unsupported output format: " <<
static_cast<int>(format);
244 absl::string_view filename,
FileFormat format) {
258 LOG(FATAL) <<
"Unsupported output format: " <<
static_cast<int>(format);
282std::string Separator() {
return absl::GetFlag(FLAGS_latex) ?
" & " :
", "; }
284std::string Eol() {
return absl::GetFlag(FLAGS_latex) ?
" \\\\\n" :
"\n"; }
290 return new_gen.
cost() / ref_gen.cost();
322 return absl::GetFlag(FLAGS_latex)
324 " & $%#.2f (%.0f) \\pm %#.2f (%.0f)$ & $[%.0f, %.0f]$",
325 stats.mean, stats.median, stats.stddev, stats.iqr, stats.min,
327 : stats.ToVerboseString(
", ");
332std::string BoldIf(
bool condition, absl::string_view str) {
333 return condition && absl::GetFlag(FLAGS_latex)
334 ? absl::StrCat(
"\\textbf{", str,
"}")
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);
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));
356 return absl::StrFormat(
"%.1f", Speedup(ref_gen, 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,
368 Separator(), FormattedSpeedup(ref_gen, 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,
379 ReportSecondPart(ref_gen, new_gen));
384 if (absl::GetFlag(FLAGS_latex)) {
385 return absl::StrCat(model.name(), Separator(), model.ToString(Separator()),
386 FormatStats(model.ComputeColumnStats()),
387 FormatStats(model.ComputeRowStats()));
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());
401void SetModelName(absl::string_view filename,
SetCoverModel* model) {
402 std::string problem_name = std::string(filename);
403 constexpr absl::string_view kTxtSuffix =
".txt";
405 if (absl::EndsWith(problem_name, kTxtSuffix)) {
406 problem_name.resize(problem_name.size() - kTxtSuffix.size());
408 if (absl::GetFlag(FLAGS_unicost) || model->is_unicost()) {
409 absl::StrAppend(&problem_name,
"*");
411 model->SetName(problem_name);
417 GeometricMean() : sum_(0.0), count_(0) {}
418 void Add(
double value) {
419 sum_ += std::log(value);
422 double Get()
const {
return std::exp(sum_ / count_); }
429std::vector<std::string> BuildVector(
const char*
const files[],
int size) {
430 return std::vector<std::string>(files, files + size);
436 "rail507.txt",
"rail516.txt",
"rail582.txt",
"rail2536.txt",
437 "rail2586.txt",
"rail4284.txt",
"rail4872.txt",
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",
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",
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",
471 "scpcyc06.txt",
"scpcyc07.txt",
"scpcyc08.txt",
472 "scpcyc09.txt",
"scpcyc10.txt",
"scpcyc11.txt",
476 "a320_coc.txt",
"a320.txt",
"alitalia.txt",
477 "b727.txt",
"sasd9imp2.txt",
"sasjump.txt",
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",
487 "accidents.dat",
"chess.dat",
"connect.dat",
"kosarak.dat",
489 "retail.dat",
"webdocs.dat",
493 std::tuple<std::string, std::vector<std::string>,
FileFormat>;
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)) {
509 all_scp_files.insert(all_scp_files.end(),
kScpNrFiles,
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},
537 QCHECK(!absl::GetFlag(FLAGS_benchmarks_dir).empty())
538 <<
"Benchmarks directory must be specified.";
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"),
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);
562 LOG(INFO) <<
"Reading " << filespec;
564 if (absl::GetFlag(FLAGS_unicost)) {
565 for (
const SubsetIndex subset : model.
SubsetRange()) {
569 SetModelName(filename, &model);
570 if (absl::GetFlag(FLAGS_stats)) {
571 std::cout << FormatModelAndStats(model) << kEol;
573 if (!absl::GetFlag(FLAGS_solve))
continue;
574 LOG(INFO) <<
"Solving " << model.
name();
584 const bool run_classic_solvers =
585 model.
num_elements() <= absl::GetFlag(FLAGS_max_elements_for_classic);
586 if (run_classic_solvers) {
597 absl::StrAppend(&output, ReportBothParts(classic, element_degree));
604 absl::StrAppend(&output, ReportBothParts(classic, lazy_element_degree));
608 absl::StrAppend(&output, ReportBothParts(steepest, lazy_steepest));
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));
630 if (absl::GetFlag(FLAGS_tlns)) {
634 for (
int i = 0; i < 500; ++i) {
635 std::vector<SubsetIndex> range =
639 if (lazy_inv.
cost() < best_cost) {
640 best_cost = lazy_inv.
cost();
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());
653 std::cout << output << kEol;
656 if (absl::GetFlag(FLAGS_summarize)) {
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;
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.";
687 <<
"Input format cannot be empty.";
689 <<
"Output format cannot be empty.";
691 if (absl::GetFlag(FLAGS_generate)) {
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));
698 if (!output.empty()) {
704 auto problem = output.empty() ?
input : output;
705 if (absl::GetFlag(FLAGS_stats)) {
708 if (absl::GetFlag(FLAGS_solve)) {
709 LOG(INFO) <<
"Solving " << problem;
716int main(
int argc,
char** argv) {
718 if (absl::GetFlag(FLAGS_benchmarks)) {
absl::Duration GetDuration() const
void Start()
When Start() is called multiple times, only the most recent is used.
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 CostOrLowerBound() const
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.
BaseInt ComputeNumSingletonRows() const
BaseInt num_elements() const
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)
void CreateSparseRowView()
BaseInt ComputeNumSingletonColumns() const
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.
BaseInt num_subsets() const
SetCoverInvariant * inv() const
std::string name() const
Returns the name of the heuristic.
Cost cost() const
Returns the current cost of the solution in the invariant.
absl::Duration run_time() const
The consistency level is maintained up to kFreeAndUncovered.
bool NextSolution(const SubsetBoolVector &in_focus) final
SteepestSearch.
void InitGoogle(absl::string_view usage, int *argc, char ***argv, bool deprecated)
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
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%.
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