From f7b0c37f0c5054004c6f0a217e9027b462fc916c Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Sat, 11 Feb 2023 04:27:31 -0800 Subject: [PATCH] fix --- examples/cpp/BUILD.bazel | 13 ---- examples/cpp/jobshop_sat.cc | 88 ++++++++++++++++------- examples/cpp/multi_knapsack_sat.cc | 1 + examples/cpp/network_routing_sat.cc | 1 + examples/cpp/qap_sat.cc | 2 +- examples/cpp/vector_bin_packing_solver.cc | 2 +- 6 files changed, 67 insertions(+), 40 deletions(-) diff --git a/examples/cpp/BUILD.bazel b/examples/cpp/BUILD.bazel index e554a93d24..b0eb4ffbdb 100644 --- a/examples/cpp/BUILD.bazel +++ b/examples/cpp/BUILD.bazel @@ -489,19 +489,6 @@ cc_binary( ], ) -cc_binary( - name = "solve", - srcs = ["solve.cc"], - deps = [ - "//ortools/base", - "//ortools/linear_solver", - "//ortools/linear_solver:linear_solver_cc_proto", - "//ortools/lp_data:model_reader", - "//ortools/lp_data:mps_reader", - "@com_google_absl//absl/time", - ], -) - cc_binary( name = "sports_scheduling_sat", srcs = ["sports_scheduling_sat.cc"], diff --git a/examples/cpp/jobshop_sat.cc b/examples/cpp/jobshop_sat.cc index 560dbe31bd..0f8cc9485a 100644 --- a/examples/cpp/jobshop_sat.cc +++ b/examples/cpp/jobshop_sat.cc @@ -139,7 +139,22 @@ void CreateJobs(const JsspInputProblem& problem, int64_t horizon, durations.push_back(task.duration(a)); } - const IntVar start = cp_model.NewIntVar(Domain(hard_start, hard_end)); + // Hack: we force the end to be below the horizon if the job has no hard + // limit defined. + // + // The correct formula should use min_duration, but this will break the + // makespan detection inside the solver. Luckily, the horizon computation + // is very loose and has a lot of slack, so we should not loose the + // optimal solution. + // + // TODO(user): remove the makespan interval when makespan detection is + // based on the dependency graph and not on the creation of the makespan + // interval. + const IntVar start = cp_model.NewIntVar(Domain( + hard_start, + job.has_latest_end() || problem.makespan_cost_per_time_unit() == 0 + ? hard_end + : hard_end - max_duration)); if (min_duration == max_duration) { const IntervalVar interval = cp_model.NewFixedSizeIntervalVar(start, min_duration); @@ -346,7 +361,7 @@ void CreateMachines( const int job_i = machine_to_tasks[m][i].job; const MachineTaskData& tail = machine_to_tasks[m][i]; - // TODO(user, lperron): simplify the code! + // TODO(user): simplify the code! CHECK_EQ(i, job_i); // Source to nodes. @@ -370,7 +385,7 @@ void CreateMachines( const MachineTaskData& head = machine_to_tasks[m][j]; const int job_j = head.job; - // TODO(user, lperron): simplify the code! + // TODO(user): simplify the code! CHECK_EQ(j, job_j); const int64_t transition = machine_transitions.transition_time(job_i * num_jobs + job_j); @@ -519,13 +534,11 @@ void AddCumulativeRelaxation( machines_per_component[components[c]].push_back(c); } LOG(INFO) << "Found " << machines_per_component.size() - << " connected machine components."; + << " connected machine components"; for (const auto& it : machines_per_component) { - // Ignore the trivial cases. - if (it.second.size() < 2 || it.second.size() == num_machines) continue; absl::flat_hash_set component(it.second.begin(), it.second.end()); - std::vector intervals; + std::vector connected_intervals; for (int j = 0; j < num_jobs; ++j) { const Job& job = problem.jobs(j); const int num_tasks_in_job = job.tasks_size(); @@ -533,27 +546,52 @@ void AddCumulativeRelaxation( const Task& task = job.tasks(t); for (const int m : task.machine()) { if (component.contains(m)) { - intervals.push_back(job_to_tasks[j][t].interval); + connected_intervals.push_back(job_to_tasks[j][t].interval); break; } } } } - LOG(INFO) << "Found machine connected component: [" - << absl::StrJoin(it.second, ", ") << "] with " << intervals.size() - << " intervals"; - // Ignore trivial case with all intervals. - if (intervals.size() == 1 || intervals.size() == num_tasks) continue; + // Ignore trivial cases with at most one interval, or all intervals, or only + // one machine. + if (connected_intervals.size() <= 1 || component.size() <= 1 || + component.size() == num_tasks) { + continue; + } + + LOG(INFO) << "Interesting machine connected component: [" + << absl::StrJoin(it.second, ", ") << "] with " + << connected_intervals.size() << " intervals"; CumulativeConstraint cumul = cp_model.AddCumulative(component.size()); - for (int i = 0; i < intervals.size(); ++i) { - cumul.AddDemand(intervals[i], 1); + for (const IntervalVar& interval : connected_intervals) { + cumul.AddDemand(interval, 1); } if (absl::GetFlag(FLAGS_use_interval_makespan)) { cumul.AddDemand(makespan_interval, component.size()); } } + + // Add a global cumulative that contains all main intervals. + // + // On most benchmarks, this is the only cumulative constraint created as the + // graph of connected interval has only one component. + // + // Even on benchmarks with cliques, it still helps, as it allows a global + // energetic reasoning that uses the makespan. + LOG(INFO) << "Add global cumulative with " << num_tasks << " intervals and " + << num_machines << " machines"; + CumulativeConstraint global_cumul = cp_model.AddCumulative(num_machines); + for (int j = 0; j < num_jobs; ++j) { + const int num_tasks_in_job = problem.jobs(j).tasks_size(); + for (int t = 0; t < num_tasks_in_job; ++t) { + global_cumul.AddDemand(job_to_tasks[j][t].interval, 1); + } + } + if (absl::GetFlag(FLAGS_use_interval_makespan)) { + global_cumul.AddDemand(makespan_interval, num_machines); + } } // This redundant linear constraints states that the sum of durations of all @@ -611,7 +649,7 @@ void DisplayJobStatistics( // Solve a JobShop scheduling problem using CP-SAT. void Solve(const JsspInputProblem& problem) { if (absl::GetFlag(FLAGS_display_model)) { - LOG(INFO) << problem.DebugString(); + LOG(INFO) << problem; } CpModelBuilder cp_model; @@ -649,16 +687,15 @@ void Solve(const JsspInputProblem& problem) { // that was slower than the interval trick when I tried. const IntVar makespan = cp_model.NewIntVar(Domain(0, horizon)); IntervalVar makespan_interval; - if (absl::GetFlag(FLAGS_use_interval_makespan)) { - makespan_interval = cp_model.NewIntervalVar( - /*start=*/makespan, - /*size=*/cp_model.NewIntVar(Domain(1, horizon)), - /*end=*/cp_model.NewIntVar(Domain(horizon + 1))); - } else if (problem.makespan_cost_per_time_unit() != 0L) { + if (problem.makespan_cost_per_time_unit() != 0L) { + if (absl::GetFlag(FLAGS_use_interval_makespan)) { + makespan_interval = cp_model.NewIntervalVar( + /*start=*/makespan, + /*size=*/cp_model.NewIntVar(Domain(1, horizon)), + /*end=*/cp_model.NewIntVar(Domain(horizon + 1))); + } for (int j = 0; j < num_jobs; ++j) { // The makespan will be greater than the end of each job. - // This is not needed if we add the makespan "interval" to each - // disjunctive. cp_model.AddLessOrEqual(job_to_tasks[j].back().end, makespan); } } @@ -675,7 +712,8 @@ void Solve(const JsspInputProblem& problem) { // Try to detect connected components of alternative machines. // If this is happens, we can add a cumulative constraint as a relaxation of // all no_ovelap constraints on the set of alternative machines. - if (absl::GetFlag(FLAGS_use_cumulative_relaxation)) { + if (absl::GetFlag(FLAGS_use_cumulative_relaxation) && + problem.makespan_cost_per_time_unit() != 0) { AddCumulativeRelaxation(problem, job_to_tasks, makespan_interval, cp_model); } diff --git a/examples/cpp/multi_knapsack_sat.cc b/examples/cpp/multi_knapsack_sat.cc index f3726d6490..3484229c79 100644 --- a/examples/cpp/multi_knapsack_sat.cc +++ b/examples/cpp/multi_knapsack_sat.cc @@ -20,6 +20,7 @@ // of each items. #include +#include #include #include "absl/flags/flag.h" diff --git a/examples/cpp/network_routing_sat.cc b/examples/cpp/network_routing_sat.cc index 9f70d37210..a9eb2a3051 100644 --- a/examples/cpp/network_routing_sat.cc +++ b/examples/cpp/network_routing_sat.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/examples/cpp/qap_sat.cc b/examples/cpp/qap_sat.cc index e1685a4e3f..6aead52dac 100644 --- a/examples/cpp/qap_sat.cc +++ b/examples/cpp/qap_sat.cc @@ -125,7 +125,7 @@ void SolveQap() { } // namespace operations_research static const char kUsageStr[] = - "Solves quadratic assigment problems with CP-SAT. " + "Solves quadratic assignment problems with CP-SAT. " "The input file should have the format described in the QAPLIB (see " "http://anjos.mgi.polymtl.ca/qaplib/)."; diff --git a/examples/cpp/vector_bin_packing_solver.cc b/examples/cpp/vector_bin_packing_solver.cc index 66601d26e0..60f46e3435 100644 --- a/examples/cpp/vector_bin_packing_solver.cc +++ b/examples/cpp/vector_bin_packing_solver.cc @@ -72,7 +72,7 @@ void ParseAndSolve(const std::string& filename, absl::string_view solver, << data.item_size() << " item types, and " << data.resource_capacity_size() << " dimensions."; if (absl::GetFlag(FLAGS_display_proto)) { - LOG(INFO) << data.DebugString(); + LOG(INFO) << data; } // Build optimization model.