replace sequence var internal module by a tsp like version

This commit is contained in:
lperron@google.com
2012-07-21 01:19:32 +00:00
parent 9e6563338c
commit 28c0f8beaa
5 changed files with 333 additions and 722 deletions

View File

@@ -1,200 +0,0 @@
// Copyright 2010-2012 Google
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This model implements a simple jobshop problem.
//
// A jobshop is a standard scheduling problem where you must schedule a
// set of jobs on a set of machines. Each job is a sequence of tasks
// (a task can only start when the preceding task finished), each of
// which occupies a single specific machine during a specific
// duration. Therefore, a job is simply given by a sequence of pairs
// (machine id, duration).
// The objective is to minimize the 'makespan', which is the duration
// between the start of the first task (across all machines) and the
// completion of the last task (across all machines).
//
// This will be modelled by sets of intervals variables (see class
// IntervalVar in constraint_solver/constraint_solver.h), one per
// task, representing the [start_time, end_time] of the task. Tasks
// in the same job will be linked by precedence constraints. Tasks on
// the same machine will be covered by Sequence constraints.
//
// Search will then be applied on the sequence constraints.
#include <cstdio>
#include <cstdlib>
#include "base/commandlineflags.h"
#include "base/commandlineflags.h"
#include "base/integral_types.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "constraint_solver/constraint_solver.h"
#include "cpp/jobshop.h"
DEFINE_string(
data_file,
"",
"Required: input file description the scheduling problem to solve, "
"in our jssp format:\n"
" - the first line is \"instance <instance name>\"\n"
" - the second line is \"<number of jobs> <number of machines>\"\n"
" - then one line per job, with a single space-separated "
"list of \"<machine index> <duration>\"\n"
"note: jobs with one task are not supported");
DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit.");
DEFINE_bool(full_debug, false, "");
namespace operations_research {
void Jobshop(const JobShopData& data) {
Solver solver("jobshop");
const int machine_count = data.machine_count();
const int job_count = data.job_count();
const int horizon = data.horizon();
// ----- Creates all Intervals and vars -----
// Stores all tasks attached interval variables per job.
std::vector<std::vector<IntervalVar*> > jobs_to_tasks(job_count);
// machines_to_tasks stores the same interval variables as above, but
// grouped my machines instead of grouped by jobs.
std::vector<std::vector<IntervalVar*> > machines_to_tasks(machine_count);
// Creates all individual interval variables.
for (int job_id = 0; job_id < job_count; ++job_id) {
const std::vector<JobShopData::Task>& tasks = data.TasksOfJob(job_id);
for (int task_index = 0; task_index < tasks.size(); ++task_index) {
const JobShopData::Task& task = tasks[task_index];
CHECK_EQ(job_id, task.job_id);
const string name = StringPrintf("J%dM%dI%dD%d",
task.job_id,
task.machine_id,
task_index,
task.duration);
IntervalVar* const one_task =
solver.MakeFixedDurationIntervalVar(0,
horizon,
task.duration,
false,
name);
jobs_to_tasks[task.job_id].push_back(one_task);
machines_to_tasks[task.machine_id].push_back(one_task);
}
}
// ----- Creates model -----
// Creates precedences inside jobs.
for (int job_id = 0; job_id < job_count; ++job_id) {
const int task_count = jobs_to_tasks[job_id].size();
for (int task_index = 0; task_index < task_count - 1; ++task_index) {
IntervalVar* const t1 = jobs_to_tasks[job_id][task_index];
IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1];
Constraint* const prec =
solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1);
solver.AddConstraint(prec);
}
}
// Adds disjunctive constraints on unary resources, and creates
// sequence variables. A sequence variable is a dedicated variable
// whose job is to sequence interval variables.
std::vector<IntVar*> all_nexts_;
std::vector<DisjunctiveConstraint*> all_machines_(machine_count);
for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
const string name = StringPrintf("Machine_%d", machine_id);
all_machines_[machine_id] =
solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id], name);
all_machines_[machine_id]->BuildNextModel();
const std::vector<IntVar*>& nexts =
all_machines_[machine_id]->NextVariables();
all_nexts_.insert(all_nexts_.end(), nexts.begin(), nexts.end());
solver.AddConstraint(all_machines_[machine_id]);
}
// Creates array of end_times of jobs.
std::vector<IntVar*> all_ends;
for (int job_id = 0; job_id < job_count; ++job_id) {
const int task_count = jobs_to_tasks[job_id].size();
IntervalVar* const task = jobs_to_tasks[job_id][task_count - 1];
all_ends.push_back(task->EndExpr()->Var());
}
// Objective: minimize the makespan (maximum end times of all tasks)
// of the problem.
IntVar* const objective_var = solver.MakeMax(all_ends)->Var();
OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1);
// ----- Search monitors and decision builder -----
// This decision builder will rank all tasks on all machines.
DecisionBuilder* const sequence_phase =
solver.MakePhase(all_nexts_,
Solver::CHOOSE_FIRST_UNBOUND,
Solver::ASSIGN_MIN_VALUE);
// After the ranking of tasks, the schedule is still loose and any
// task can be postponed at will. But, because the problem is now a PERT
// (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
// we can schedule each task at its earliest start time. This is
// conveniently done by fixing the objective variable to its
// minimum value.
DecisionBuilder* const obj_phase =
solver.MakePhase(objective_var,
Solver::CHOOSE_FIRST_UNBOUND,
Solver::ASSIGN_MIN_VALUE);
// The main decision builder (ranks all tasks, then fixes the
// objective_variable).
DecisionBuilder* const main_phase =
solver.Compose(sequence_phase, obj_phase);
// Search log.
const int kLogFrequency = 1000000;
SearchMonitor* const search_log =
solver.MakeSearchLog(kLogFrequency, objective_monitor);
SearchLimit* limit = NULL;
if (FLAGS_time_limit_in_ms > 0) {
limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms);
}
// Search.
solver.NewSearch(main_phase, search_log, objective_monitor, limit);
while (solver.NextSolution()) {
if (FLAGS_full_debug) {
for (int i = 0; i < machine_count; ++i) {
all_machines_[i]->FullDebug();
}
}
}
solver.EndSearch();
}
} // namespace operations_research
static const char kUsage[] =
"Usage: see flags.\nThis program runs a simple job shop optimization "
"output besides the debug LOGs of the solver.";
int main(int argc, char **argv) {
google::SetUsageMessage(kUsage);
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_data_file.empty()) {
LOG(FATAL) << "Please supply a data file with --data_file=";
}
operations_research::JobShopData data;
data.Load(FLAGS_data_file);
operations_research::Jobshop(data);
return 0;
}

View File

@@ -672,12 +672,6 @@ $(OBJ_DIR)/jobshop_ls.$O:$(EX_DIR)/cpp/jobshop_ls.cc $(SRC_DIR)/constraint_solve
$(BIN_DIR)/jobshop_ls$E: $(CP_DEPS) $(OBJ_DIR)/jobshop_ls.$O
$(CCC) $(CFLAGS) $(OBJ_DIR)/jobshop_ls.$O $(CP_LNK) $(LDFLAGS) $(EXEOUT)jobshop_ls$E
$(OBJ_DIR)/jobshop_next.$O:$(EX_DIR)/cpp/jobshop_next.cc $(SRC_DIR)/constraint_solver/constraint_solver.h
$(CCC) $(CFLAGS) -c $(EX_DIR)$Scpp/jobshop_next.cc $(OBJ_OUT)jobshop_next.$O
$(BIN_DIR)/jobshop_next$E: $(CP_DEPS) $(OBJ_DIR)/jobshop_next.$O
$(CCC) $(CFLAGS) $(OBJ_DIR)/jobshop_next.$O $(CP_LNK) $(LDFLAGS) $(EXEOUT)jobshop_next$E
$(OBJ_DIR)/jobshop_earlytardy.$O:$(EX_DIR)/cpp/jobshop_earlytardy.cc $(SRC_DIR)/constraint_solver/constraint_solver.h $(EX_DIR)/cpp/jobshop_earlytardy.h
$(CCC) $(CFLAGS) -c $(EX_DIR)$Scpp/jobshop_earlytardy.cc $(OBJ_OUT)jobshop_earlytardy.$O

View File

@@ -4153,8 +4153,10 @@ class SequenceVar : public PropagationBaseObject {
public:
SequenceVar(Solver* const s,
const IntervalVar* const * intervals,
const IntVar* const * nexts,
int size,
const string& name);
virtual ~SequenceVar();
virtual string DebugString() const;
@@ -4171,12 +4173,10 @@ class SequenceVar : public PropagationBaseObject {
// unranked interval vars in the sequence.
void ActiveHorizonRange(int64* const hmin, int64* const hmax) const;
// Returns the number of interval vars already ranked.
int Ranked() const;
// Returns the number of not-unperformed interval vars that may be
// performed and that are not yet ranked.
int NotRanked() const;
// Compute statistics on the sequence.
void ComputeStatistics(int* const ranked,
int* const not_ranked,
int* const unperformed) const;
// Ranks the index_th interval var first of all unranked interval
// vars. After that, it will no longer be considered ranked.
@@ -4194,9 +4194,9 @@ class SequenceVar : public PropagationBaseObject {
// of all currently unranked interval vars.
void RankNotLast(int index);
// Adds a precedence relation (STARTS_AFTER_END) between two
// activities of the sequence var.
void AddPrecedence(int before, int after);
// // Adds a precedence relation (STARTS_AFTER_END) between two
// // activities of the sequence var.
// void AddPrecedence(int before, int after);
// Computes the set of indices of interval variables that can be
// ranked first in the set of unranked activities.
@@ -4227,6 +4227,9 @@ class SequenceVar : public PropagationBaseObject {
// Returns the index_th interval of the sequence.
IntervalVar* Interval(int index) const;
// Returns the index_th next of the sequence.
IntVar* Next(int index) const;
// Returns the number of interval vars in the sequence.
int size() const { return size_; }
@@ -4234,33 +4237,15 @@ class SequenceVar : public PropagationBaseObject {
virtual void Accept(ModelVisitor* const visitor) const;
private:
bool IsBefore(int before, int after) const;
bool IsActive(int index) const;
void ComputeRanks(const std::vector<int>& rank_first,
const std::vector<int>& rank_last);
void AddPrecedences(const std::vector<int>& rank_first,
const std::vector<int>& rank_last);
void ComputeTransitiveClosure(const std::vector<int>& rank_first,
const std::vector<int>& rank_last);
void MarkUnperformed(const std::vector<int>& unperformed);
int ComputeForwardFrontier();
int ComputeBackwardFrontier();
void UpdatePrevious() const;
scoped_array<IntervalVar*> intervals_;
scoped_array<IntVar*> nexts_;
const int size_;
// For each interval, it counts the number of intervals we know are
// before this one.
RevArray<int> ranked_before_;
// Number of intervals ranked first.
NumericalRev<int> count_ranked_first_;
// For each interval, it counts the number of intervals we know are
// after this one.
NumericalRevArray<int> ranked_after_;
// Number of intervals ranked last.
NumericalRev<int> count_ranked_last_;
// Bi-dimensional bitset. bitset(i, j) if you know that i is before j.
scoped_ptr<RevBitMatrix> precedence_matrix_;
// Temporary sets during propagation.
hash_set<int> first_set_;
hash_set<int> last_set_;
const int next_size_;
mutable std::vector<int> previous_;
};
// ----- Class Set Var -----
@@ -4945,14 +4930,6 @@ class DisjunctiveConstraint : public Constraint {
// Creates a sequence variable from the constraint.
virtual SequenceVar* MakeSequenceVar() = 0;
virtual void BuildNextModel() = 0;
#if !defined(SWIG)
virtual const std::vector<IntVar*>& NextVariables() const = 0;
#endif
virtual void FullDebug() = 0;
protected:
scoped_array<IntervalVar*> intervals_;
const int size_;

View File

@@ -803,16 +803,19 @@ class FullDisjunctiveConstraint : public DisjunctiveConstraint {
virtual SequenceVar* MakeSequenceVar() {
if (sequence_var_ == NULL) {
BuildNextModel();
solver()->SaveValue(reinterpret_cast<void**>(&sequence_var_));
sequence_var_ = solver()->RevAlloc(new SequenceVar(solver(),
intervals_.get(),
nexts_.data(),
size_,
name()));
}
return sequence_var_;
}
virtual void BuildNextModel() {
private:
void BuildNextModel() {
Solver* const s = solver();
const string& ct_name = name();
const int num_nodes = size_ + 1;
@@ -826,14 +829,10 @@ class FullDisjunctiveConstraint : public DisjunctiveConstraint {
s->AddConstraint(s->MakeAllDifferent(nexts_));
actives_.resize(num_nodes);
std::vector<IntVar*> performed(size_);
for (int i = 0; i < size_; ++i) {
actives_[i + 1] = intervals_[i]->PerformedExpr()->Var();
performed[i] = actives_[i + 1];
}
// actives_[0] = s->MakeIsDifferentCstVar(nexts_[0], Zero());
// s->AddConstraint(s->MakeMaxEquality(performed, actives_[0]));
// actives_[0]->SetValue(true);
// TODO(lperron): Only if at least one activity is always performed.
actives_[0] = s->MakeIntConst(1);
// No Cycle.
@@ -856,18 +855,6 @@ class FullDisjunctiveConstraint : public DisjunctiveConstraint {
s->MakePathCumul(nexts_, actives_, time_cumuls_, time_transits_));
}
virtual const std::vector<IntVar*>& NextVariables() const {
return nexts_;
}
virtual void FullDebug() {
LOG(INFO) << "nexts = " << DebugStringVector(nexts_, ", ");
LOG(INFO) << "actives = " << DebugStringVector(actives_, ", ");
LOG(INFO) << "time cumuls = " << DebugStringVector(time_cumuls_, ", ");
LOG(INFO) << "time transits = " << DebugStringVector(time_transits_, ", ");
}
private:
SequenceVar* sequence_var_;
EdgeFinderAndDetectablePrecedences straight_;
EdgeFinderAndDetectablePrecedences mirror_;
@@ -1654,7 +1641,7 @@ class CumulativeConstraint : public Constraint {
}
void Accept(ModelVisitor* const visitor) const {
// TODO(user): Build arrays on demaand?
// TODO(user): Build arrays on demand?
visitor->BeginVisitConstraint(ModelVisitor::kCumulative, this);
visitor->VisitIntervalArrayArgument(ModelVisitor::kIntervalsArgument,
intervals_.get(),
@@ -1811,416 +1798,6 @@ DisjunctiveConstraint* Solver::MakeDisjunctiveConstraint(
name));
}
// ----- SequenceVar -----
// TODO(user): Add better class invariants, in particular checks
// that ranked_first, ranked_last, and unperformed are truly disjoint.
// TODO(user): The current representation has many flaws and is
// dense by nature. This is visible in the RankSequence and
// ComputePossibleFirstsAndLasts().
SequenceVar::SequenceVar(Solver* const s,
const IntervalVar* const * intervals,
int size,
const string& name)
: PropagationBaseObject(s),
intervals_(new IntervalVar*[size]),
size_(size),
ranked_before_(size, 0),
count_ranked_first_(0),
ranked_after_(size, 0),
count_ranked_last_(0),
precedence_matrix_(new RevBitMatrix(size_, size_)) {
memcpy(intervals_.get(), intervals, size_ * sizeof(*intervals));
set_name(name);
}
SequenceVar::~SequenceVar() {}
IntervalVar* SequenceVar::Interval(int index) const {
return intervals_[index];
}
string SequenceVar::DebugString() const {
int64 hmin, hmax, dmin, dmax;
HorizonRange(&hmin, &hmax);
DurationRange(&dmin, &dmax);
return StringPrintf("%s(horizon = %" GG_LL_FORMAT
"d..%" GG_LL_FORMAT
"d, duration = %" GG_LL_FORMAT
"d..%" GG_LL_FORMAT
"d, not ranked = %d, ranked = %d)",
name().c_str(),
hmin,
hmax,
dmin,
dmax,
NotRanked(),
Ranked());
}
void SequenceVar::Accept(ModelVisitor* const visitor) const {
visitor->VisitSequenceVariable(this);
}
void SequenceVar::DurationRange(int64* const dmin, int64* const dmax) const {
int64 dur_min = 0;
int64 dur_max = 0;
for (int i = 0; i < size_; ++i) {
IntervalVar* const t = intervals_[i];
if (t->MayBePerformed()) {
if (t->MustBePerformed()) {
dur_min += t->DurationMin();
}
dur_max += t->DurationMax();
}
}
*dmin = dur_min;
*dmax = dur_max;
}
void SequenceVar::HorizonRange(int64* const hmin, int64* const hmax) const {
int64 hor_min = kint64max;
int64 hor_max = kint64min;
for (int i = 0; i < size_; ++i) {
IntervalVar* const t = intervals_[i];
if (t->MayBePerformed()) {
const int64 tmin = t->StartMin();
const int64 tmax = t->EndMax();
if (tmin < hor_min) {
hor_min = tmin;
}
if (tmax > hor_max) {
hor_max = tmax;
}
}
}
*hmin = hor_min;
*hmax = hor_max;
}
bool SequenceVar::IsActive(int index) const {
return (intervals_[index]->MayBePerformed() &&
ranked_before_[index] >= count_ranked_first_.Value() &&
ranked_after_[index] >= count_ranked_last_.Value());
}
void SequenceVar::ActiveHorizonRange(int64* const hmin,
int64* const hmax) const {
int64 hor_min = kint64max;
int64 hor_max = kint64min;
for (int interval_index = 0; interval_index < size_; ++interval_index) {
IntervalVar* const t = intervals_[interval_index];
if (IsActive(interval_index)) {
const int64 tmin = t->StartMin();
const int64 tmax = t->EndMax();
if (tmin < hor_min) {
hor_min = tmin;
}
if (tmax > hor_max) {
hor_max = tmax;
}
}
}
*hmin = hor_min;
*hmax = hor_max;
}
int SequenceVar::Ranked() const {
return count_ranked_last_.Value() + count_ranked_first_.Value();
}
int SequenceVar::NotRanked() const {
int count = 0;
for (int interval_index = 0; interval_index < size_; ++interval_index) {
if (IsActive(interval_index)) {
count++;
}
}
return count;
}
void SequenceVar::ComputePossibleFirstsAndLasts(
std::vector<int>* const possible_firsts,
std::vector<int>* const possible_lasts) {
for (int candidate = 0; candidate < size_; ++candidate) {
// We skip unperformed activities.
if (!intervals_[candidate]->MayBePerformed()) {
continue;
}
if (ranked_before_[candidate] == count_ranked_first_.Value()) {
int ranked_before = 0;
int ranked_after = 0;
for (int other = 0; other < size_; ++other) {
if (other != candidate && intervals_[other]->MustBePerformed()) {
if (IsBefore(other, candidate)) {
ranked_before++;
} else if (IsBefore(candidate, other)) {
ranked_after++;
}
}
}
if (ranked_before > count_ranked_first_.Value()) {
ranked_before_.SetValue(solver(), candidate, ranked_before);
} else {
possible_firsts->push_back(candidate);
}
if (ranked_after > count_ranked_last_.Value()) {
ranked_after_.SetValue(solver(), candidate, ranked_after);
} else {
possible_lasts->push_back(candidate);
}
}
}
}
bool SequenceVar::IsBefore(int before, int after) const {
return precedence_matrix_->IsSet(before, after) ||
intervals_[before]->StartMax() < intervals_[after]->EndMin();
}
void SequenceVar::AddPrecedence(int before, int after) {
if (IsBefore(after, before)) {
solver()->Fail();
}
if (IsBefore(before, after)) { // nothing to do.
return;
}
IntervalVar* const before_interval = intervals_[before];
IntervalVar* const after_interval = intervals_[after];
solver()->AddConstraint(
solver()->MakeIntervalVarRelation(after_interval,
Solver::STARTS_AFTER_END,
before_interval));
precedence_matrix_->SetToOne(solver(), before, after);
}
void SequenceVar::MarkUnperformed(const std::vector<int>& unperformed) {
for (ConstIter<std::vector<int> > it(unperformed); !it.at_end(); ++it) {
intervals_[*it]->SetPerformed(false);
}
}
void SequenceVar::ComputeRanks(const std::vector<int>& rank_first,
const std::vector<int>& rank_last) {
// rank_before_ on elements of rank_first.
for (int i = 0; i < rank_first.size(); ++i) {
const int index = rank_first[i];
intervals_[index]->SetPerformed(true);
ranked_before_.SetValue(solver(), index, i);
}
// rank_after_ on elements of rank_last.
for (int i = 0; i < rank_last.size(); ++i) {
const int index = rank_last[i];
intervals_[index]->SetPerformed(true);
ranked_after_.SetValue(solver(), index, i);
}
// rank_before_ and rank_after_ on the rest.
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->MayBePerformed()) {
if (!ContainsKey(first_set_, i)) {
ranked_before_.SetValue(solver(), i, rank_first.size());
}
if (!ContainsKey(last_set_, i)) {
ranked_after_.SetValue(solver(), i, rank_last.size());
}
}
}
}
void SequenceVar::AddPrecedences(const std::vector<int>& rank_first,
const std::vector<int>& rank_last) {
// Adds precedences on the forward chain.
for (int i = 1; i < rank_first.size(); ++i) {
// Only one constraint on the chain.
AddPrecedence(rank_first[i - 1], rank_first[i]);
}
// Adds precedences on the backward chain.
for (int i = 1; i < rank_last.size(); ++i) {
// Only one constraint on the chain.
AddPrecedence(rank_last[i], rank_last[i - 1]);
}
// All unranked activities are bounded by the end of the chains.
for (int i = 0; i < size_; ++i) {
if (!ContainsKey(first_set_, i) && !rank_first.empty()) {
AddPrecedence(rank_first.back(), i);
}
if (!ContainsKey(last_set_, i) && !rank_last.empty()) {
AddPrecedence(i, rank_last.back());
}
}
}
void SequenceVar::ComputeTransitiveClosure(const std::vector<int>& rank_first,
const std::vector<int>& rank_last) {
// Marks the transitive closure of the dependencies induced by the chains.
// TODO(user): Remove this function and improve
// ComputePossibleFirstsAndLasts().
hash_set<int> ranked;
// Forward.
for (ConstIter<std::vector<int> > it(rank_first); !it.at_end(); ++it) {
ranked.insert(*it);
for (int j = 0; j < size_; ++j) {
if (!ContainsKey(ranked, j)) {
precedence_matrix_->SetToOne(solver(), *it, j);
}
}
}
ranked.clear();
// Backward.
for (ConstIter<std::vector<int> > it(rank_last); !it.at_end(); ++it) {
ranked.insert(*it);
for (int j = 0; j < size_; ++j) {
if (!ContainsKey(ranked, j)) {
precedence_matrix_->SetToOne(solver(), j, *it);
}
}
}
}
void SequenceVar::RankSequence(const std::vector<int>& rank_first,
const std::vector<int>& rank_last,
const std::vector<int>& unperformed) {
solver()->GetPropagationMonitor()->RankSequence(this,
rank_first,
rank_last,
unperformed);
MarkUnperformed(unperformed);
// Collects intervals in the two vectors.
first_set_.clear();
first_set_.insert(rank_first.begin(), rank_first.end());
last_set_.clear();
last_set_.insert(rank_last.begin(), rank_last.end());
// Apply changes.
ComputeRanks(rank_first, rank_last);
AddPrecedences(rank_first, rank_last);
ComputeTransitiveClosure(rank_first, rank_last);
// Stores count_ranked_first_ and count_ranked_last_.
count_ranked_first_.SetValue(solver(), rank_first.size());
count_ranked_last_.SetValue(solver(), rank_last.size());
}
void SequenceVar::RankFirst(int index) {
solver()->GetPropagationMonitor()->RankFirst(this, index);
IntervalVar* const t = intervals_[index];
t->SetPerformed(true);
Solver* const s = solver();
for (int i = 0; i < size_; ++i) {
if (i != index &&
ranked_before_[i] >= count_ranked_first_.Value() &&
intervals_[i]->MayBePerformed()) {
if (ranked_before_[i] == count_ranked_first_.Value()) {
ranked_before_.SetValue(s, i, count_ranked_first_.Value() + 1);
}
AddPrecedence(index, i);
}
}
ranked_before_.SetValue(s, index, count_ranked_first_.Value());
count_ranked_first_.SetValue(s, count_ranked_first_.Value() + 1);
}
void SequenceVar::RankNotFirst(int index) {
solver()->GetPropagationMonitor()->RankNotFirst(this, index);
ranked_before_.SetValue(solver(), index, count_ranked_first_.Value() + 1);
int possible_first = -1;
for (int i = 0; i < size_; ++i) {
if (ranked_before_[i] == count_ranked_first_.Value() &&
intervals_[i]->MayBePerformed()) {
if (possible_first != -1) {
return;
}
possible_first = i;
}
}
if (possible_first == -1) {
solver()->Fail();
}
if (intervals_[possible_first]->MustBePerformed()) {
RankFirst(possible_first);
}
}
void SequenceVar::RankLast(int index) {
solver()->GetPropagationMonitor()->RankLast(this, index);
IntervalVar* const t = intervals_[index];
t->SetPerformed(true);
Solver* const s = solver();
for (int i = 0; i < size_; ++i) {
if (i != index &&
ranked_after_[i] >= count_ranked_last_.Value() &&
intervals_[i]->MayBePerformed()) {
if (ranked_after_[i] == count_ranked_last_.Value()) {
ranked_after_.SetValue(s, i, count_ranked_last_.Value() + 1);
}
AddPrecedence(i, index);
}
}
ranked_after_.SetValue(s, index, count_ranked_last_.Value());
count_ranked_last_.SetValue(s, count_ranked_last_.Value() + 1);
}
void SequenceVar::RankNotLast(int index) {
solver()->GetPropagationMonitor()->RankNotLast(this, index);
ranked_after_.SetValue(solver(), index, count_ranked_last_.Value() + 1);
int possible_last = -1;
for (int i = 0; i < size_; ++i) {
if (ranked_after_[i] == count_ranked_last_.Value() &&
intervals_[i]->MayBePerformed()) {
if (possible_last != -1) {
return;
}
possible_last = i;
}
}
if (possible_last == -1) {
solver()->Fail();
}
if (intervals_[possible_last]->MustBePerformed()) {
RankLast(possible_last);
}
}
void SequenceVar::FillSequence(std::vector<int>* const rank_first,
std::vector<int>* const rank_last,
std::vector<int>* const unperformed) const {
CHECK_NOTNULL(rank_first);
CHECK_NOTNULL(rank_last);
CHECK_NOTNULL(unperformed);
rank_first->clear();
rank_first->resize(count_ranked_first_.Value(), -1);
rank_last->clear();
rank_last->resize(count_ranked_last_.Value(), -1);
int min_filled = 0;
int max_filled = 0;
for (int var_index = 0; var_index < size_; ++var_index) {
if (intervals_[var_index]->MustBePerformed()) {
const int ranked_before = ranked_before_[var_index];
const int ranked_after = ranked_after_[var_index];
if (ranked_before < count_ranked_first_.Value()) {
DCHECK_EQ(-1, (*rank_first)[ranked_before]);
(*rank_first)[ranked_before] = var_index;
min_filled++;
} else if (ranked_after < count_ranked_last_.Value()) {
(*rank_last)[ranked_after] = var_index;
max_filled++;
}
} else if (!intervals_[var_index]->MayBePerformed()) {
unperformed->push_back(var_index);
}
}
DCHECK_EQ(count_ranked_first_.Value(), min_filled);
DCHECK_EQ(count_ranked_last_.Value(), max_filled);
if (unperformed->size() + rank_first->size() + rank_last->size() == size_) {
// All ranked, pushing everything to rank_first;
while (!rank_last->empty()) {
rank_first->push_back(rank_last->back());
rank_last->pop_back();
}
}
}
// ----- Public class -----
DisjunctiveConstraint::DisjunctiveConstraint(Solver* const s,

View File

@@ -20,10 +20,282 @@
#include "base/scoped_ptr.h"
#include "base/stringprintf.h"
#include "constraint_solver/constraint_solver.h"
#include "constraint_solver/constraint_solveri.h"
#include "util/string_array.h"
namespace operations_research {
// ----- SequenceVar -----
// TODO(user): Add better class invariants, in particular checks
// that ranked_first, ranked_last, and unperformed are truly disjoint.
SequenceVar::SequenceVar(Solver* const s,
const IntervalVar* const * intervals,
const IntVar* const * nexts,
int size,
const string& name)
: PropagationBaseObject(s),
intervals_(new IntervalVar*[size]),
nexts_(new IntVar*[size + 1]),
size_(size),
next_size_(size + 1),
previous_(size + 2, -1) {
memcpy(intervals_.get(), intervals, size_ * sizeof(*intervals));
memcpy(nexts_.get(), nexts, next_size_ * sizeof(*nexts));
set_name(name);
}
SequenceVar::~SequenceVar() {}
IntervalVar* SequenceVar::Interval(int index) const {
return intervals_[index];
}
IntVar* SequenceVar::Next(int index) const {
return nexts_[index];
}
string SequenceVar::DebugString() const {
int64 hmin, hmax, dmin, dmax;
HorizonRange(&hmin, &hmax);
DurationRange(&dmin, &dmax);
int unperformed = 0;
int ranked = 0;
int not_ranked = 0;
ComputeStatistics(&ranked, &not_ranked, &unperformed);
return StringPrintf("%s(horizon = %" GG_LL_FORMAT
"d..%" GG_LL_FORMAT
"d, duration = %" GG_LL_FORMAT
"d..%" GG_LL_FORMAT
"d, not ranked = %d, ranked = %d)",
name().c_str(),
hmin,
hmax,
dmin,
dmax,
not_ranked,
ranked);
}
void SequenceVar::Accept(ModelVisitor* const visitor) const {
visitor->VisitSequenceVariable(this);
}
void SequenceVar::DurationRange(int64* const dmin, int64* const dmax) const {
int64 dur_min = 0;
int64 dur_max = 0;
for (int i = 0; i < size_; ++i) {
IntervalVar* const t = intervals_[i];
if (t->MayBePerformed()) {
if (t->MustBePerformed()) {
dur_min += t->DurationMin();
}
dur_max += t->DurationMax();
}
}
*dmin = dur_min;
*dmax = dur_max;
}
void SequenceVar::HorizonRange(int64* const hmin, int64* const hmax) const {
int64 hor_min = kint64max;
int64 hor_max = kint64min;
for (int i = 0; i < size_; ++i) {
IntervalVar* const t = intervals_[i];
if (t->MayBePerformed()) {
IntervalVar* const t = intervals_[i];
hor_min = std::min(hor_min, t->StartMin());
hor_max = std::max(hor_max, t->StartMax());
}
}
*hmin = hor_min;
*hmax = hor_max;
}
void SequenceVar::ActiveHorizonRange(int64* const hmin,
int64* const hmax) const {
hash_set<int> decided;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->CannotBePerformed()) {
decided.insert(i);
}
}
int first = 0;
while (nexts_[first]->Bound()) {
first = nexts_[first]->Min();
if (first < next_size_) {
decided.insert(first - 1);
} else {
break;
}
}
if (first != next_size_) {
UpdatePrevious();
int last = next_size_;
while (previous_[last] != -1) {
last = previous_[last];
decided.insert(last - 1);
}
}
int64 hor_min = kint64max;
int64 hor_max = kint64min;
for (int i = 0; i < size_; ++i) {
if (!ContainsKey(decided, i)) {
IntervalVar* const t = intervals_[i];
hor_min = std::min(hor_min, t->StartMin());
hor_max = std::max(hor_max, t->StartMax());
}
}
*hmin = hor_min;
*hmax = hor_max;
}
void SequenceVar::ComputeStatistics(int* const ranked,
int* const not_ranked,
int* const unperformed) const {
*unperformed = 0;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->CannotBePerformed()) {
(*unperformed)++;
}
}
*ranked = 0;
int first = 0;
while (first < next_size_ && nexts_[first]->Bound()) {
first = nexts_[first]->Min();
(*ranked)++;
}
if (first != next_size_) {
UpdatePrevious();
int last = next_size_;
while (previous_[last] != -1) {
last = previous_[last];
(*ranked)++;
}
}
*not_ranked = size_ - *ranked - *unperformed;
}
int SequenceVar::ComputeForwardFrontier() {
int first = 0;
while (nexts_[first]->Bound()) {
first = nexts_[first]->Min();
}
return first;
}
int SequenceVar::ComputeBackwardFrontier() {
UpdatePrevious();
int last = next_size_;
while (previous_[last] != -1) {
last = previous_[last];
}
return last;
}
void SequenceVar::ComputePossibleFirstsAndLasts(
std::vector<int>* const possible_firsts,
std::vector<int>* const possible_lasts) {
IntVar* const forward_var = nexts_[ComputeForwardFrontier()];
for (int64 i = forward_var->Min(); i <= forward_var->Max(); ++i) {
// TODO(lperron): use domain iterator.
if (i != 0 && i < size_ + 1 && forward_var->Contains(i)) {
possible_firsts->push_back(i - 1);
}
}
// TODO(lperron) : backward
}
void SequenceVar::RankSequence(const std::vector<int>& rank_first,
const std::vector<int>& rank_last,
const std::vector<int>& unperformed) {
solver()->GetPropagationMonitor()->RankSequence(this,
rank_first,
rank_last,
unperformed);
// Mark unperformed.
for (ConstIter<std::vector<int> > it(unperformed); !it.at_end(); ++it) {
intervals_[*it]->SetPerformed(false);
}
// Forward.
int forward = 0;
for (int i = 0; i < rank_first.size(); ++i) {
const int next = 1 + rank_first[i];
nexts_[forward]->SetValue(next);
forward = next;
}
// TODO(lperron) : backward.
}
void SequenceVar::RankFirst(int index) {
solver()->GetPropagationMonitor()->RankFirst(this, index);
const int forward_frontier = ComputeForwardFrontier();
nexts_[forward_frontier]->SetValue(index + 1);
}
void SequenceVar::RankNotFirst(int index) {
solver()->GetPropagationMonitor()->RankNotFirst(this, index);
const int forward_frontier = ComputeForwardFrontier();
nexts_[forward_frontier]->RemoveValue(index + 1);
}
void SequenceVar::RankLast(int index) {
solver()->GetPropagationMonitor()->RankLast(this, index);
const int backward_frontier = ComputeBackwardFrontier();
nexts_[index + 1]->SetValue(backward_frontier);
}
void SequenceVar::RankNotLast(int index) {
solver()->GetPropagationMonitor()->RankNotLast(this, index);
const int backward_frontier = ComputeBackwardFrontier();
nexts_[index + 1]->RemoveValue(backward_frontier);
}
void SequenceVar::UpdatePrevious() const {
for (int i = 0; i < size_ + 2; ++i) {
previous_[i] = -1;
}
for (int i = 0; i < next_size_; ++i) {
if (nexts_[i]->Bound()) {
previous_[nexts_[i]->Min()] = i;
}
}
}
void SequenceVar::FillSequence(std::vector<int>* const rank_first,
std::vector<int>* const rank_last,
std::vector<int>* const unperformed) const {
CHECK_NOTNULL(rank_first);
CHECK_NOTNULL(rank_last);
CHECK_NOTNULL(unperformed);
rank_first->clear();
rank_last->clear();
unperformed->clear();
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->CannotBePerformed()) {
unperformed->push_back(i);
}
}
int first = 0;
while (nexts_[first]->Bound()) {
first = nexts_[first]->Min();
if (first < next_size_) {
rank_first->push_back(first - 1);
} else {
break;
}
}
if (first != next_size_) {
UpdatePrevious();
int last = next_size_;
while (previous_[last] != -1) {
last = previous_[last];
rank_last->push_back(last - 1);
}
}
}
// ----- Decisions and DecisionBuilders on interval vars -----
// TODO(user) : treat optional intervals
@@ -198,31 +470,25 @@ class RankFirstIntervalVars : public DecisionBuilder {
virtual Decision* Next(Solver* const s) {
SequenceVar* best_sequence = NULL;
std::vector<int> best_possible_firsts;
best_possible_firsts_.clear();
while (true) {
if (FindSequenceVar(s, &best_sequence, &best_possible_firsts)) {
if (FindSequenceVar(s, &best_sequence)) {
// No not create a choice point if it is not needed.
DCHECK(best_sequence != NULL);
if (best_possible_firsts.size() == 1 &&
if (best_possible_firsts_.size() == 1 &&
best_sequence->Interval(
best_possible_firsts.back())->MustBePerformed()) {
best_sequence->RankFirst(best_possible_firsts.back());
best_possible_firsts_.back())->MustBePerformed()) {
best_sequence->RankFirst(best_possible_firsts_.back());
continue;
}
int best_interval = -1;
if (!FindIntervalVar(s,
best_sequence,
best_possible_firsts,
&best_interval)) {
if (!FindIntervalVar(s, best_sequence, &best_interval)) {
s->Fail();
}
CHECK_NE(-1, best_interval);
return s->RevAlloc(new RankFirst(best_sequence, best_interval));
} else {
for (int i = 0; i < size_; ++i) {
CHECK_EQ(0, sequences_[i]->NotRanked()) << sequences_[i]->DebugString();
}
return NULL;
return NULL;
}
}
}
@@ -239,12 +505,11 @@ class RankFirstIntervalVars : public DecisionBuilder {
// Selects the interval var to rank.
bool FindIntervalVarOnStartMin(Solver* const s,
SequenceVar* const best_sequence,
const std::vector<int>& best_possible_firsts,
int* const best_interval_index) {
int best_interval = -1;
int64 best_start_min = kint64max;
for (int index = 0; index < best_possible_firsts.size(); ++index) {
const int candidate = best_possible_firsts[index];
for (int index = 0; index < best_possible_firsts_.size(); ++index) {
const int candidate = best_possible_firsts_[index];
IntervalVar* const interval = best_sequence->Interval(candidate);
if (interval->StartMin() < best_start_min) {
best_interval = candidate;
@@ -261,17 +526,15 @@ class RankFirstIntervalVars : public DecisionBuilder {
bool FindIntervalVarRandomly(Solver* const s,
SequenceVar* const best_sequence,
const std::vector<int>& best_possible_firsts,
int* const best_interval_index) {
DCHECK(!best_possible_firsts.empty());
const int index = s->Rand32(best_possible_firsts.size());
*best_interval_index = best_possible_firsts[index];
DCHECK(!best_possible_firsts_.empty());
const int index = s->Rand32(best_possible_firsts_.size());
*best_interval_index = best_possible_firsts_[index];
return true;
}
bool FindIntervalVar(Solver* const s,
SequenceVar* const best_sequence,
const std::vector<int>& best_possible_firsts,
int* const best_interval_index) {
switch (strategy_) {
case Solver::SEQUENCE_DEFAULT:
@@ -279,12 +542,10 @@ class RankFirstIntervalVars : public DecisionBuilder {
case Solver::CHOOSE_MIN_SLACK_RANK_FORWARD:
return FindIntervalVarOnStartMin(s,
best_sequence,
best_possible_firsts,
best_interval_index);
case Solver::CHOOSE_RANDOM_RANK_FORWARD:
return FindIntervalVarRandomly(s,
best_sequence,
best_possible_firsts,
best_interval_index);
default:
LOG(FATAL) << "Unknown strategy " << strategy_;
@@ -293,20 +554,23 @@ class RankFirstIntervalVars : public DecisionBuilder {
// Selects the sequence var to start ranking.
bool FindSequenceVarOnSlack(Solver* const s,
SequenceVar** const best_sequence,
std::vector<int>* const best_possible_firsts) const {
SequenceVar** const best_sequence) const {
int64 best_slack = kint64max;
int64 best_ahmin = kint64max;
*best_sequence = NULL;
best_possible_firsts->clear();
best_possible_firsts_.clear();
for (int i = 0; i < size_; ++i) {
SequenceVar* const candidate_sequence = sequences_[i];
if (candidate_sequence->NotRanked() > 0) {
int ranked = 0;
int not_ranked = 0;
int unperformed = 0;
candidate_sequence->ComputeStatistics(&ranked, &not_ranked, &unperformed);
if (not_ranked > 0) {
std::vector<int> candidate_possible_firsts;
std::vector<int> candidate_possible_Lasts;
std::vector<int> candidate_possible_lasts;
candidate_sequence->ComputePossibleFirstsAndLasts(
&candidate_possible_firsts,
&candidate_possible_Lasts);
&candidate_possible_lasts);
// No possible first, failing.
if (candidate_possible_firsts.size() == 0) {
s->Fail();
@@ -316,7 +580,7 @@ class RankFirstIntervalVars : public DecisionBuilder {
candidate_sequence->Interval(
candidate_possible_firsts.back())->MustBePerformed()) {
*best_sequence = candidate_sequence;
*best_possible_firsts = candidate_possible_firsts;
best_possible_firsts_ = candidate_possible_firsts;
return true;
}
@@ -331,7 +595,7 @@ class RankFirstIntervalVars : public DecisionBuilder {
(current_slack == best_slack && ahmin < best_ahmin)) {
best_slack = current_slack;
*best_sequence = candidate_sequence;
*best_possible_firsts = candidate_possible_firsts;
best_possible_firsts_ = candidate_possible_firsts;
best_ahmin = ahmin;
}
}
@@ -340,18 +604,21 @@ class RankFirstIntervalVars : public DecisionBuilder {
}
bool FindSequenceVarRandomly(Solver* const s,
SequenceVar** const best_sequence,
std::vector<int>* const best_possible_firsts) const {
SequenceVar** const best_sequence) const {
std::vector<int> all_candidates;
std::vector<std::vector<int> > all_possible_firsts;
for (int i = 0; i < size_; ++i) {
SequenceVar* const candidate_sequence = sequences_[i];
if (candidate_sequence->NotRanked() > 0) {
int ranked = 0;
int not_ranked = 0;
int unperformed = 0;
candidate_sequence->ComputeStatistics(&ranked, &not_ranked, &unperformed);
if (not_ranked > 0) {
std::vector<int> candidate_possible_firsts;
std::vector<int> candidate_possible_Lasts;
std::vector<int> candidate_possible_lasts;
candidate_sequence->ComputePossibleFirstsAndLasts(
&candidate_possible_firsts,
&candidate_possible_Lasts);
&candidate_possible_lasts);
// No possible first, failing.
if (candidate_possible_firsts.size() == 0) {
s->Fail();
@@ -361,7 +628,7 @@ class RankFirstIntervalVars : public DecisionBuilder {
candidate_sequence->Interval(
candidate_possible_firsts.back())->MustBePerformed()) {
*best_sequence = candidate_sequence;
*best_possible_firsts = candidate_possible_firsts;
best_possible_firsts_ = candidate_possible_firsts;
return true;
}
@@ -374,24 +641,19 @@ class RankFirstIntervalVars : public DecisionBuilder {
}
const int chosen = s->Rand32(all_candidates.size());
*best_sequence = sequences_[all_candidates[chosen]];
*best_possible_firsts = all_possible_firsts[chosen];
best_possible_firsts_ = all_possible_firsts[chosen];
return true;
}
bool FindSequenceVar(Solver* const s,
SequenceVar** const best_sequence,
std::vector<int>* const best_possible_firsts) const {
SequenceVar** const best_sequence) const {
switch (strategy_) {
case Solver::SEQUENCE_DEFAULT:
case Solver::SEQUENCE_SIMPLE:
case Solver::CHOOSE_MIN_SLACK_RANK_FORWARD:
return FindSequenceVarOnSlack(s,
best_sequence,
best_possible_firsts);
return FindSequenceVarOnSlack(s, best_sequence);
case Solver::CHOOSE_RANDOM_RANK_FORWARD:
return FindSequenceVarRandomly(s,
best_sequence,
best_possible_firsts);
return FindSequenceVarRandomly(s, best_sequence);
default:
LOG(FATAL) << "Unknown strategy " << strategy_;
}
@@ -400,6 +662,7 @@ class RankFirstIntervalVars : public DecisionBuilder {
scoped_array<SequenceVar*> sequences_;
const int size_;
const Solver::SequenceStrategy strategy_;
mutable std::vector<int> best_possible_firsts_;
};
} // namespace