Skip to content

Commit

Permalink
[CP-SAT] revisit search heuristics, lns workers search heuristics; sc…
Browse files Browse the repository at this point in the history
…heduling propagation
  • Loading branch information
lperron committed Oct 2, 2023
1 parent 4255e18 commit d07127d
Show file tree
Hide file tree
Showing 17 changed files with 355 additions and 184 deletions.
42 changes: 13 additions & 29 deletions examples/cpp/jobshop_sat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "absl/flags/flag.h"
#include "absl/log/check.h"
#include "absl/strings/str_join.h"
#include "absl/types/span.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/wrappers.pb.h"
#include "ortools/base/init_google.h"
Expand Down Expand Up @@ -197,7 +198,7 @@ struct AlternativeTaskData {
// main task of the job.
void CreateAlternativeTasks(
const JsspInputProblem& problem,
const std::vector<std::vector<JobTaskData>>& job_to_tasks, int64_t horizon,
absl::Span<const std::vector<JobTaskData>> job_to_tasks, int64_t horizon,
std::vector<std::vector<std::vector<AlternativeTaskData>>>&
job_task_to_alternatives,
CpModelBuilder& cp_model) {
Expand Down Expand Up @@ -296,7 +297,7 @@ struct MachineTaskData {

std::vector<std::vector<MachineTaskData>> GetDataPerMachine(
const JsspInputProblem& problem,
const std::vector<std::vector<std::vector<AlternativeTaskData>>>&
absl::Span<const std::vector<std::vector<AlternativeTaskData>>>
job_task_to_alternatives) {
const int num_jobs = problem.jobs_size();
const int num_machines = problem.machines_size();
Expand Down Expand Up @@ -442,8 +443,8 @@ void CreateMachines(
// Collect all objective terms and add them to the model.
void CreateObjective(
const JsspInputProblem& problem,
const std::vector<std::vector<JobTaskData>>& job_to_tasks,
const std::vector<std::vector<std::vector<AlternativeTaskData>>>&
absl::Span<const std::vector<JobTaskData>> job_to_tasks,
absl::Span<const std::vector<std::vector<AlternativeTaskData>>>
job_task_to_alternatives,
int64_t horizon, IntVar makespan, CpModelBuilder& cp_model) {
LinearExpr objective;
Expand Down Expand Up @@ -512,7 +513,7 @@ void CreateObjective(
// and not the alternate copies.
void AddCumulativeRelaxation(
const JsspInputProblem& problem,
const std::vector<std::vector<JobTaskData>>& job_to_tasks,
absl::Span<const std::vector<JobTaskData>> job_to_tasks,
IntervalVar makespan_interval, CpModelBuilder& cp_model) {
const int num_jobs = problem.jobs_size();
const int num_machines = problem.machines_size();
Expand Down Expand Up @@ -579,33 +580,13 @@ void AddCumulativeRelaxation(
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
// tasks is a lower bound of the makespan * number of machines.
void AddMakespanRedundantConstraints(
const JsspInputProblem& problem,
const std::vector<std::vector<JobTaskData>>& job_to_tasks, IntVar makespan,
absl::Span<const std::vector<JobTaskData>> job_to_tasks, IntVar makespan,
CpModelBuilder& cp_model) {
const int num_machines = problem.machines_size();

Expand All @@ -621,8 +602,8 @@ void AddMakespanRedundantConstraints(

void DisplayJobStatistics(
const JsspInputProblem& problem, int64_t horizon,
const std::vector<std::vector<JobTaskData>>& job_to_tasks,
const std::vector<std::vector<std::vector<AlternativeTaskData>>>&
absl::Span<const std::vector<JobTaskData>> job_to_tasks,
absl::Span<const std::vector<std::vector<AlternativeTaskData>>>
job_task_to_alternatives) {
const int num_jobs = job_to_tasks.size();
int num_tasks = 0;
Expand Down Expand Up @@ -747,7 +728,7 @@ void Solve(const JsspInputProblem& problem) {
// CP-SAT now has a default strategy for scheduling problem that works best.

if (absl::GetFlag(FLAGS_display_sat_model)) {
LOG(INFO) << cp_model.Proto().DebugString();
LOG(INFO) << cp_model.Proto();
}

// Setup parameters.
Expand All @@ -766,6 +747,9 @@ void Solve(const JsspInputProblem& problem) {
parameters.add_extra_subsolvers("objective_shaving_search");
}

// Tells the solver we have a makespan objective.
parameters.set_push_all_tasks_toward_start(true);

const CpSolverResponse response =
SolveWithParameters(cp_model.Build(), parameters);

Expand Down
3 changes: 2 additions & 1 deletion examples/cpp/knapsack_2d_sat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <vector>

#include "absl/flags/flag.h"
#include "absl/types/span.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
Expand All @@ -43,7 +44,7 @@ namespace sat {
void CheckAndPrint2DSolution(
const CpSolverResponse& response,
const packing::MultipleDimensionsBinPackingProblem& problem,
const std::vector<std::vector<IntervalVar>>& interval_by_item_dimension,
absl::Span<const std::vector<IntervalVar>> interval_by_item_dimension,
std::string* solution_in_ascii_form) {
const int num_items = problem.items_size();

Expand Down
21 changes: 11 additions & 10 deletions examples/cpp/slitherlink_sat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "absl/flags/parse.h"
#include "absl/log/check.h"
#include "absl/strings/str_format.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/sat/cp_model.h"
#include "ortools/sat/cp_model.pb.h"
Expand All @@ -29,9 +30,9 @@
namespace operations_research {
namespace sat {

void PrintSolution(const std::vector<std::vector<int> >& data,
const std::vector<std::vector<bool> >& h_arcs,
const std::vector<std::vector<bool> >& v_arcs) {
void PrintSolution(absl::Span<const std::vector<int>> data,
absl::Span<const std::vector<bool>> h_arcs,
absl::Span<const std::vector<bool>> v_arcs) {
const int num_rows = data.size();
const int num_columns = data[0].size();

Expand Down Expand Up @@ -65,7 +66,7 @@ void PrintSolution(const std::vector<std::vector<int> >& data,
std::cout << last_line << std::endl;
}

void SlitherLink(const std::vector<std::vector<int> >& data) {
void SlitherLink(const std::vector<std::vector<int>>& data) {
const int num_rows = data.size();
const int num_columns = data[0].size();

Expand Down Expand Up @@ -201,7 +202,7 @@ void SlitherLink(const std::vector<std::vector<int> >& data) {

const CpSolverResponse response = Solve(builder.Build());

std::vector<std::vector<bool> > h_arcs(num_rows + 1);
std::vector<std::vector<bool>> h_arcs(num_rows + 1);
for (int y = 0; y < num_rows + 1; ++y) {
for (int x = 0; x < num_columns; ++x) {
const int arc = undirected_horizontal_arc(x, y);
Expand All @@ -211,7 +212,7 @@ void SlitherLink(const std::vector<std::vector<int> >& data) {
}
}

std::vector<std::vector<bool> > v_arcs(num_columns + 1);
std::vector<std::vector<bool>> v_arcs(num_columns + 1);
for (int y = 0; y < num_rows; ++y) {
for (int x = 0; x < num_columns + 1; ++x) {
const int arc = undirected_vertical_arc(x, y);
Expand All @@ -230,18 +231,18 @@ void SlitherLink(const std::vector<std::vector<int> >& data) {

int main(int argc, char** argv) {
absl::ParseCommandLine(argc, argv);
const std::vector<std::vector<int> > tiny = {{3, 3, 1}};
const std::vector<std::vector<int>> tiny = {{3, 3, 1}};

const std::vector<std::vector<int> > small = {
const std::vector<std::vector<int>> small = {
{3, 2, -1, 3}, {-1, -1, -1, 2}, {3, -1, -1, -1}, {3, -1, 3, 1}};

const std::vector<std::vector<int> > medium = {
const std::vector<std::vector<int>> medium = {
{-1, 0, -1, 1, -1, -1, 1, -1}, {-1, 3, -1, -1, 2, 3, -1, 2},
{-1, -1, 0, -1, -1, -1, -1, 0}, {-1, 3, -1, -1, 0, -1, -1, -1},
{-1, -1, -1, 3, -1, -1, 0, -1}, {1, -1, -1, -1, -1, 3, -1, -1},
{3, -1, 1, 3, -1, -1, 3, -1}, {-1, 0, -1, -1, 3, -1, 3, -1}};

const std::vector<std::vector<int> > big = {
const std::vector<std::vector<int>> big = {
{3, -1, -1, -1, 2, -1, 1, -1, 1, 2},
{1, -1, 0, -1, 3, -1, 2, 0, -1, -1},
{-1, 3, -1, -1, -1, -1, -1, -1, 3, -1},
Expand Down
10 changes: 6 additions & 4 deletions examples/cpp/weighted_tardiness_sat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "absl/flags/flag.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "absl/types/span.h"
#include "ortools/base/init_google.h"
#include "ortools/base/logging.h"
#include "ortools/sat/cp_model.h"
Expand All @@ -36,9 +37,9 @@ namespace operations_research {
namespace sat {

// Solve a single machine problem with weighted tardiness cost.
void Solve(const std::vector<int64_t>& durations,
const std::vector<int64_t>& due_dates,
const std::vector<int64_t>& weights) {
void Solve(absl::Span<const int64_t> durations,
absl::Span<const int64_t> due_dates,
absl::Span<const int64_t> weights) {
const int num_tasks = durations.size();
CHECK_EQ(due_dates.size(), num_tasks);
CHECK_EQ(weights.size(), num_tasks);
Expand Down Expand Up @@ -163,7 +164,8 @@ void Solve(const std::vector<int64_t>& durations,
Model model;
model.Add(NewSatParameters(absl::GetFlag(FLAGS_params)));
model.GetOrCreate<SatParameters>()->set_log_search_progress(true);
model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) {
model.Add(NewFeasibleSolutionObserver([&, due_dates, durations,
weights](const CpSolverResponse& r) {
// Note that we compute the "real" cost here and do not use the tardiness
// variables. This is because in the core based approach, the tardiness
// variable might be fixed before the end date, and we just have a >=
Expand Down
18 changes: 13 additions & 5 deletions examples/python/rcpsp_sat.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,24 +530,32 @@ def SolveRcpsp(

# Solve model.
solver = cp_model.CpSolver()
if not _USE_INTERVAL_MAKESPAN.value:
solver.parameters.exploit_all_precedences = True
solver.parameters.use_hard_precedences_in_cumulative = True

# Parse user specified parameters.
if params:
text_format.Parse(params, solver.parameters)

# Favor objective_shaving_search over objective_lb_search.
if solver.parameters.num_workers >= 16 and solver.parameters.num_workers < 24:
solver.parameters.ignore_subsolvers.append("objective_lb_search")
solver.parameters.extra_subsolvers.append("objective_shaving_search")

# Experimental: Specify the fact that the objective is a makespan
solver.parameters.push_all_tasks_toward_start = True

# Enable logging in the main solve.

if in_main_solve:
solver.parameters.log_search_progress = True
#
status = solver.Solve(model)
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
assignment = rcpsp_pb2.RcpspAssignment()
for t, _ in enumerate(problem.tasks):
if t in task_starts:
assignment.start_of_task.append(solver.Value(task_starts[t]))
for r in range(len(task_to_presence_literals[t])):
if solver.BooleanValue(task_to_presence_literals[t][r]):
for r, recipe_literal in enumerate(task_to_presence_literals[t]):
if solver.BooleanValue(recipe_literal):
assignment.selected_recipe_of_task.append(r)
break
else: # t is not an active task.
Expand Down
76 changes: 40 additions & 36 deletions ortools/sat/cp_model_lns.cc
Original file line number Diff line number Diff line change
Expand Up @@ -418,23 +418,22 @@ bool NeighborhoodGeneratorHelper::IntervalIsActive(
return false;
}

void NeighborhoodGeneratorHelper::FilterInactiveIntervals(
std::vector<int> NeighborhoodGeneratorHelper::KeepActiveIntervals(
absl::Span<const int> unfiltered_intervals,
const CpSolverResponse& initial_solution,
std::vector<int>* filtered_intervals) const {
filtered_intervals->clear();
const CpSolverResponse& initial_solution) const {
std::vector<int> filtered_intervals;
filtered_intervals.reserve(unfiltered_intervals.size());
absl::ReaderMutexLock lock(&domain_mutex_);
for (const int i : unfiltered_intervals) {
if (IntervalIsActive(i, initial_solution)) filtered_intervals->push_back(i);
if (IntervalIsActive(i, initial_solution)) filtered_intervals.push_back(i);
}
return filtered_intervals;
}

std::vector<int> NeighborhoodGeneratorHelper::GetActiveIntervals(
const CpSolverResponse& initial_solution) const {
std::vector<int> result;
FilterInactiveIntervals(TypeToConstraints(ConstraintProto::kInterval),
initial_solution, &result);
return result;
return KeepActiveIntervals(TypeToConstraints(ConstraintProto::kInterval),
initial_solution);
}

std::vector<std::pair<int, int>>
Expand Down Expand Up @@ -2024,6 +2023,19 @@ Neighborhood RandomPrecedenceSchedulingNeighborhoodGenerator::Generate(
precedences, initial_solution, helper_);
}

namespace {
void AppendVarsFromAllIntervalIndices(absl::Span<const int> indices,
absl::Span<const int> all_intervals,
const CpModelProto& model_proto,
std::vector<int>* variables) {
for (const int index : indices) {
const std::vector<int> vars =
UsedVariables(model_proto.constraints(all_intervals[index]));
variables->insert(variables->end(), vars.begin(), vars.end());
}
}
} // namespace

Neighborhood SchedulingTimeWindowNeighborhoodGenerator::Generate(
const CpSolverResponse& initial_solution, double difficulty,
absl::BitGenRef random) {
Expand All @@ -2038,21 +2050,17 @@ Neighborhood SchedulingTimeWindowNeighborhoodGenerator::Generate(
std::vector<int> intervals_to_relax;
intervals_to_relax.reserve(partition.selected_indices.size());
std::vector<int> variables_to_fix;
for (const int index : partition.selected_indices) {
intervals_to_relax.push_back(active_intervals[index]);
}
intervals_to_relax.insert(intervals_to_relax.end(),
partition.selected_indices.begin(),
partition.selected_indices.end());

if (helper_.Parameters().push_all_tasks_toward_start()) {
for (const int index : partition.indices_before_selected) {
const int interval = active_intervals[index];
// Remove the interval so that we do not use it for precedences.
intervals_to_relax.push_back(interval);

// Fix all variables.
const std::vector<int> vars =
UsedVariables(helper_.ModelProto().constraints(interval));
variables_to_fix.insert(variables_to_fix.end(), vars.begin(), vars.end());
}
intervals_to_relax.insert(intervals_to_relax.end(),
partition.indices_before_selected.begin(),
partition.indices_before_selected.end());
AppendVarsFromAllIntervalIndices(partition.indices_before_selected,
active_intervals, helper_.ModelProto(),
&variables_to_fix);
}

gtl::STLSortAndRemoveDuplicates(&intervals_to_relax);
Expand All @@ -2068,25 +2076,21 @@ Neighborhood SchedulingResourceWindowsNeighborhoodGenerator::Generate(
std::vector<int> variables_to_fix;
std::vector<int> active_intervals;
for (const std::vector<int>& intervals : intervals_in_constraints_) {
helper_.FilterInactiveIntervals(intervals, initial_solution,
&active_intervals);
active_intervals = helper_.KeepActiveIntervals(intervals, initial_solution);
const TimePartition partition = PartitionIndicesAroundRandomTimeWindow(
active_intervals, helper_.ModelProto(), initial_solution, difficulty,
random);
for (const int index : partition.selected_indices) {
intervals_to_relax.push_back(intervals[index]);
}
intervals_to_relax.insert(intervals_to_relax.end(),
partition.selected_indices.begin(),
partition.selected_indices.end());

if (helper_.Parameters().push_all_tasks_toward_start()) {
for (const int index : partition.indices_before_selected) {
const int interval = intervals[index];
// Remove the interval so that we do not use it for precedences.
intervals_to_relax.push_back(interval);
const std::vector<int> vars =
UsedVariables(helper_.ModelProto().constraints(interval));
variables_to_fix.insert(variables_to_fix.end(), vars.begin(),
vars.end());
}
intervals_to_relax.insert(intervals_to_relax.end(),
partition.indices_before_selected.begin(),
partition.indices_before_selected.end());
AppendVarsFromAllIntervalIndices(partition.indices_before_selected,
active_intervals, helper_.ModelProto(),
&variables_to_fix);
}
}

Expand Down
Loading

0 comments on commit d07127d

Please sign in to comment.