This commit is contained in:
Laurent Perron
2023-02-11 04:27:31 -08:00
parent 25c6847377
commit f7b0c37f0c
6 changed files with 67 additions and 40 deletions

View File

@@ -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"],

View File

@@ -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<int> component(it.second.begin(), it.second.end());
std::vector<IntervalVar> intervals;
std::vector<IntervalVar> 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);
}

View File

@@ -20,6 +20,7 @@
// of each items.
#include <cstdint>
#include <string>
#include <vector>
#include "absl/flags/flag.h"

View File

@@ -28,6 +28,7 @@
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <map>
#include <random>
#include <string>
#include <utility>

View File

@@ -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/).";

View File

@@ -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.