set times backward in scheduling; speedup in local search

This commit is contained in:
lperron@google.com
2014-01-17 18:36:18 +00:00
parent 2efbd55b52
commit d493e97540
6 changed files with 296 additions and 63 deletions

View File

@@ -2575,6 +2575,8 @@ void DecisionVisitor::VisitSplitVariableDomain(IntVar* const var, int64 value,
void DecisionVisitor::VisitUnknownDecision() {}
void DecisionVisitor::VisitScheduleOrPostpone(IntervalVar* const var,
int64 est) {}
void DecisionVisitor::VisitScheduleOrExpedite(IntervalVar* const var,
int64 est) {}
void DecisionVisitor::VisitRankFirstInterval(SequenceVar* const sequence,
int index) {}

View File

@@ -520,11 +520,19 @@ class Solver {
CHOOSE_RANDOM_RANK_FORWARD,
};
// Used for scheduling. Not yet implemented.
// This enum describes the straregy used to select the next interval variable
// and its value to be fixed.
enum IntervalStrategy {
// The default is INTERVAL_SET_TIMES_FORWARD.
INTERVAL_DEFAULT,
// The simple is INTERVAL_SET_TIMES_FORWARD.
INTERVAL_SIMPLE,
INTERVAL_SET_TIMES_FORWARD
// Selects the variable with the lowest starting time of all variables,
// and fixes its starting time to this lowest value.
INTERVAL_SET_TIMES_FORWARD,
// Selects the variable with the highest ending time of all variables,
// and fixes the ending time to this highest values.
INTERVAL_SET_TIMES_BACKWARD
};
// This enum is used in Solver::MakeOperator to specify the neighborhood to
@@ -2409,6 +2417,14 @@ class Solver {
Decision* MakeScheduleOrPostpone(IntervalVar* const var, int64 est,
int64* const marker);
// Returns a decision that tries to schedule a task at a given time.
// On the Apply branch, it will set that interval var as performed and set
// its end to 'est'. On the Refute branch, it will just update the
// 'marker' to 'est' - 1. This decision is used in the
// INTERVAL_SET_TIMES_BACKWARD strategy.
Decision* MakeScheduleOrExpedite(IntervalVar* const var, int64 est,
int64* const marker);
// Returns a decision that tries to rank first the ith interval var
// in the sequence variable.
Decision* MakeRankFirstInterval(SequenceVar* const sequence, int index);
@@ -3138,6 +3154,7 @@ class DecisionVisitor : public BaseObject {
virtual void VisitSplitVariableDomain(IntVar* const var, int64 value,
bool start_with_lower_half);
virtual void VisitScheduleOrPostpone(IntervalVar* const var, int64 est);
virtual void VisitScheduleOrExpedite(IntervalVar* const var, int64 est);
virtual void VisitRankFirstInterval(SequenceVar* const sequence, int index);
virtual void VisitRankLastInterval(SequenceVar* const sequence, int index);
virtual void VisitUnknownDecision();
@@ -4601,16 +4618,30 @@ class AssignmentContainer {
return Find(var, &index);
}
E* MutableElement(const V* const var) {
E* const element = MutableElementOrNull(var);
DCHECK(element != nullptr) << "Unknown variable " << var->DebugString()
<< " in solution";
return element;
}
E* MutableElementOrNull(const V* const var) {
int index = -1;
const bool found = Find(var, &index);
CHECK(found) << "Unknown variable " << var->DebugString() << " in solution";
return MutableElement(index);
if (Find(var, &index)) {
return MutableElement(index);
}
return nullptr;
}
const E& Element(const V* const var) const {
const E* const element = ElementPtrOrNull(var);
DCHECK(element != nullptr) << "Unknown variable " << var->DebugString()
<< " in solution";
return *element;
}
const E* ElementPtrOrNull(const V* const var) const {
int index = -1;
const bool found = Find(var, &index);
CHECK(found) << "Unknown variable " << var->DebugString() << " in solution";
return Element(index);
if (Find(var, &index)) {
return &Element(index);
}
return nullptr;
}
const std::vector<E>& elements() const { return elements_; }
E* MutableElement(int index) { return &elements_[index]; }
@@ -4665,9 +4696,23 @@ class AssignmentContainer {
}
}
bool Find(const V* const var, int* index) const {
EnsureMapIsUpToDate();
DCHECK_EQ(elements_map_.size(), elements_.size());
return FindCopy(elements_map_, var, index);
// This threshold was determined from microbenchmarks on Nehalem platform.
const size_t kMaxSizeForLinearAccess = 11;
if (Size() <= kMaxSizeForLinearAccess) {
// Look for 'var' in the container by performing a linear search, avoiding
// the access to (and creation of) the elements hash table.
for (int i = 0; i < elements_.size(); ++i) {
if (var == elements_[i].Var()) {
*index = i;
return true;
}
}
return false;
} else {
EnsureMapIsUpToDate();
DCHECK_EQ(elements_map_.size(), elements_.size());
return FindCopy(elements_map_, var, index);
}
}
std::vector<E> elements_;

View File

@@ -146,7 +146,7 @@ void GreaterEqExprCst::Post() {
demon_ = solver()->MakeConstraintInitialPropagateCallback(this);
expr_->WhenRange(demon_);
} else {
// Let's clean the demon in case the constraints is posted during search.
// Let's clean the demon in case the constraint is posted during search.
demon_ = nullptr;
}
}
@@ -220,7 +220,7 @@ void LessEqExprCst::Post() {
demon_ = solver()->MakeConstraintInitialPropagateCallback(this);
expr_->WhenRange(demon_);
} else {
// Let's clean the demon in case the constraints is posted during search.
// Let's clean the demon in case the constraint is posted during search.
demon_ = nullptr;
}
}

View File

@@ -382,7 +382,8 @@ class RoutingModel {
typedef _RoutingModel_DisjunctionIndex DisjunctionIndex;
typedef ResultCallback1<int64, int64> VehicleEvaluator;
typedef ResultCallback2<int64, NodeIndex, NodeIndex> NodeEvaluator2;
typedef std::vector<std::pair<int, int> > NodePairs;
typedef std::pair<int, int> NodePair;
typedef std::vector<NodePair> NodePairs;
struct CostClass {
// arc_cost_evaluator->Run(from, to) is the transit cost of arc
@@ -1485,11 +1486,19 @@ class CheapestInsertionFilteredDecisionBuilder
virtual ~CheapestInsertionFilteredDecisionBuilder() {}
protected:
typedef std::pair<int64, int64> ValuedPosition;
// Inserts 'node' just after 'predecessor', and just before 'successor',
// resulting in the following subsequence: predecessor -> node -> successor.
// If 'node' is part of a disjunction, other nodes of the disjunction are made
// unperformed.
void InsertBetween(int64 node, int64 predecessor, int64 successor);
// Helper method to the ComputeEvaluatorSortedPositions* methods. Finds all
// possible insertion positions of node 'node_to_insert' in the partial route
// starting at node 'start' and adds them to 'valued_position', a list of
// unsorted pairs of (cost, position to insert the node).
void AppendEvaluatedPositionsAfter(
int64 node_to_insert, int64 start, int64 next_after_start,
std::vector<ValuedPosition>* valued_positions);
std::unique_ptr<ResultCallback2<int64, int64, int64> > evaluator_;
};
@@ -1509,13 +1518,18 @@ class GlobalCheapestInsertionFilteredDecisionBuilder
virtual bool BuildSolution();
private:
// Computes the possible insertions for all non-inserted nodes and sorts them
// according to the current cost evaluator.
// Each std::pair<int64, int64> of the output represents an already performed node,
typedef std::pair<int64, int64> InsertionPosition;
// Computes the possible insertion positions for all non-inserted nodes and
// sorts them according to the current cost evaluator.
// Each InsertionPosition of the output represents an already performed node,
// followed by a non-inserted node that should be set as the "Next" of the
// former.
void ComputeEvaluatorSortedInsertions(
std::vector<std::pair<int64, int64> >* sorted_insertions);
void ComputeEvaluatorSortedPositions(
std::vector<InsertionPosition>* sorted_positions);
// Same as above but limited to pickup and delivery pairs. Each pair of
// InsertionPosition applies respectively to a pickup and its delivery.
void ComputeEvaluatorSortedPositionPairs(
std::vector<std::pair<InsertionPosition, InsertionPosition> >* sorted_positions);
};
// Filtered-base decision builder which builds a solution by inserting
@@ -1547,11 +1561,6 @@ class LocalCheapestInsertionFilteredDecisionBuilder
void ComputeEvaluatorSortedPositionsOnRouteAfter(
int64 node, int64 start, int64 next_after_start,
std::vector<int64>* sorted_positions);
// Helper method to the ComputeEvaluatorSortedPositions* methods; the output
// is a list of unsorted pairs of (cost, position to insert the node).
void AppendEvaluatedPositionsAfter(
int64 node_to_insert, int64 start, int64 next_after_start,
std::vector<std::pair<int64, int64> >* valued_positions);
};
// Filtered-base decision builder based on the addition heuristic, extending

View File

@@ -277,10 +277,10 @@ int64 BasePathFilter::GetNext(const Assignment::IntContainer& container,
int64 node) const {
const IntVar* const next_var = Var(node);
int64 next = IsVarSynced(node) ? Value(node) : kUnassigned;
if (container.Contains(next_var)) {
const IntVarElement& element = container.Element(next_var);
if (element.Bound()) {
next = element.Value();
const IntVarElement* const element = container.ElementPtrOrNull(next_var);
if (element != nullptr) {
if (element->Bound()) {
next = element->Value();
} else {
return kUnassigned;
}
@@ -945,6 +945,24 @@ void CheapestInsertionFilteredDecisionBuilder::InsertBetween(int64 node,
MakeDisjunctionNodesUnperformed(node);
}
void
CheapestInsertionFilteredDecisionBuilder::AppendEvaluatedPositionsAfter(
int64 node_to_insert, int64 start, int64 next_after_start,
std::vector<ValuedPosition>* valued_positions) {
CHECK(valued_positions != nullptr);
int64 insert_after = start;
while (!model()->IsEnd(insert_after)) {
const int64 insert_before =
(insert_after == start) ? next_after_start : Value(insert_after);
valued_positions->push_back(
std::make_pair(evaluator_->Run(insert_after, node_to_insert) +
evaluator_->Run(node_to_insert, insert_before) -
evaluator_->Run(insert_after, insert_before),
insert_after));
insert_after = insert_before;
}
}
namespace {
template <class T>
void SortAndExtractPairSeconds(std::vector<std::pair<int64, T>>* pairs,
@@ -971,12 +989,38 @@ bool GlobalCheapestInsertionFilteredDecisionBuilder::BuildSolution() {
if (!InitializeRoutes()) {
return false;
}
// Node insertions currently being considered.
std::vector<std::pair<int64, int64>> insertions;
// Node pair insertions currently being considered.
std::vector<std::pair<std::pair<int64, int64>, std::pair<int64, int64>>> insertion_pairs;
bool found = true;
while (found) {
found = false;
ComputeEvaluatorSortedInsertions(&insertions);
ComputeEvaluatorSortedPositionPairs(&insertion_pairs);
for (const std::pair<std::pair<int64, int64>, std::pair<int64, int64>>& insertion_pair :
insertion_pairs) {
const int64 pickup = insertion_pair.first.second;
const int64 pickup_insertion = insertion_pair.first.first;
const int64 pickup_insertion_next = Value(pickup_insertion);
InsertBetween(pickup, pickup_insertion, pickup_insertion_next);
const int64 delivery = insertion_pair.second.second;
const int64 delivery_insertion = insertion_pair.second.first;
DCHECK_NE(delivery_insertion, pickup_insertion);
const int64 delivery_insertion_next =
(delivery_insertion == pickup) ? pickup_insertion_next
: Value(delivery_insertion);
InsertBetween(delivery, delivery_insertion, delivery_insertion_next);
if (Commit()) {
found = true;
break;
}
}
}
// Node insertions currently being considered.
std::vector<std::pair<int64, int64>> insertions;
// Iterating on remaining nodes.
found = true;
while (found) {
found = false;
ComputeEvaluatorSortedPositions(&insertions);
for (const std::pair<int64, int64> insertion : insertions) {
InsertBetween(insertion.second, insertion.first, Value(insertion.first));
if (Commit()) {
@@ -990,30 +1034,71 @@ bool GlobalCheapestInsertionFilteredDecisionBuilder::BuildSolution() {
}
void GlobalCheapestInsertionFilteredDecisionBuilder::
ComputeEvaluatorSortedInsertions(
std::vector<std::pair<int64, int64>>* sorted_insertions) {
CHECK(sorted_insertions != nullptr);
sorted_insertions->clear();
std::vector<std::pair<int64, std::pair<int64, int64>>> valued_insertions;
ComputeEvaluatorSortedPositions(
std::vector<InsertionPosition>* sorted_positions) {
CHECK(sorted_positions != nullptr);
sorted_positions->clear();
std::vector<std::pair<int64, InsertionPosition>> valued_insertions;
for (int node = 0; node < model()->Size(); ++node) {
if (Contains(node)) {
continue;
}
std::vector<ValuedPosition> valued_positions;
for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
int64 insert_after = model()->Start(vehicle);
while (!model()->IsEnd(insert_after)) {
const int64 insert_before = Value(insert_after);
valued_insertions.push_back(
std::make_pair(evaluator_->Run(insert_after, node) +
evaluator_->Run(node, insert_before),
std::make_pair(insert_after, node)));
insert_after = insert_before;
const int64 start = model()->Start(vehicle);
AppendEvaluatedPositionsAfter(node, start, Value(start),
&valued_positions);
}
for (const std::pair<int64, int64>& valued_position : valued_positions) {
valued_insertions.push_back(std::make_pair(valued_position.first,
std::make_pair(valued_position.second,
node)));
}
}
SortAndExtractPairSeconds(&valued_insertions, sorted_positions);
}
void GlobalCheapestInsertionFilteredDecisionBuilder::
ComputeEvaluatorSortedPositionPairs(
std::vector<std::pair<InsertionPosition, InsertionPosition>>* sorted_positions) {
CHECK(sorted_positions != nullptr);
sorted_positions->clear();
std::vector<std::pair<int64, std::pair<InsertionPosition, InsertionPosition>>>
valued_positions;
for (const RoutingModel::NodePair node_pair :
model()->GetPickupAndDeliveryPairs()) {
const int64 pickup = node_pair.first;
const int64 delivery = node_pair.second;
if (Contains(pickup) || Contains(delivery)) {
continue;
}
for (int vehicle = 0; vehicle < model()->vehicles(); ++vehicle) {
std::vector<ValuedPosition> valued_pickup_positions;
const int64 start = model()->Start(vehicle);
AppendEvaluatedPositionsAfter(pickup, start, Value(start),
&valued_pickup_positions);
for (const ValuedPosition& valued_pickup_position :
valued_pickup_positions) {
const int64 pickup_position = valued_pickup_position.second;
CHECK(!model()->IsEnd(pickup_position));
std::vector<ValuedPosition> valued_delivery_positions;
AppendEvaluatedPositionsAfter(delivery, pickup,
Value(pickup_position),
&valued_delivery_positions);
for (const ValuedPosition& valued_delivery_position :
valued_delivery_positions) {
valued_positions.push_back(std::make_pair(
valued_pickup_position.first + valued_delivery_position.first,
std::make_pair(std::make_pair(pickup_position, pickup),
std::make_pair(valued_delivery_position.second, delivery))));
}
}
}
}
SortAndExtractPairSeconds(&valued_insertions, sorted_insertions);
SortAndExtractPairSeconds(&valued_positions, sorted_positions);
}
// LocalCheapestInsertionFilteredDecisionBuilder
LocalCheapestInsertionFilteredDecisionBuilder::
@@ -1122,23 +1207,6 @@ void LocalCheapestInsertionFilteredDecisionBuilder::
}
}
void
LocalCheapestInsertionFilteredDecisionBuilder::AppendEvaluatedPositionsAfter(
int64 node_to_insert, int64 start, int64 next_after_start,
std::vector<std::pair<int64, int64>>* valued_positions) {
CHECK(valued_positions != nullptr);
int64 insert_after = start;
while (!model()->IsEnd(insert_after)) {
const int64 insert_before =
(insert_after == start) ? next_after_start : Value(insert_after);
valued_positions->push_back(
std::make_pair(evaluator_->Run(insert_after, node_to_insert) +
evaluator_->Run(node_to_insert, insert_before),
insert_after));
insert_after = insert_before;
}
}
// CheapestAdditionFilteredDecisionBuilder
CheapestAdditionFilteredDecisionBuilder::

View File

@@ -380,6 +380,9 @@ void SequenceVar::FillSequence(std::vector<int>* const rank_first,
// TODO(user) : treat optional intervals
// TODO(user) : Call DecisionVisitor and pass name of variable
namespace {
//
// Forward scheduling.
//
class ScheduleOrPostpone : public Decision {
public:
ScheduleOrPostpone(IntervalVar* const var, int64 est, int64* const marker)
@@ -467,6 +470,96 @@ class SetTimesForward : public DecisionBuilder {
std::vector<int64> markers_;
};
//
// Backward scheduling.
//
class ScheduleOrExpedite : public Decision {
public:
ScheduleOrExpedite(IntervalVar* const var, int64 est, int64* const marker)
: var_(var), est_(est), marker_(marker) {}
virtual ~ScheduleOrExpedite() {}
virtual void Apply(Solver* const s) {
var_->SetPerformed(true);
if (est_.Value() > var_->EndMax()) {
est_.SetValue(s, var_->EndMax());
}
var_->SetEndRange(est_.Value(), est_.Value());
}
virtual void Refute(Solver* const s) {
s->SaveAndSetValue(marker_, est_.Value() - 1);
}
virtual void Accept(DecisionVisitor* const visitor) const {
CHECK(visitor != nullptr);
visitor->VisitScheduleOrExpedite(var_, est_.Value());
}
virtual std::string DebugString() const {
return StringPrintf("ScheduleOrExpedite(%s at %" GG_LL_FORMAT "d)",
var_->DebugString().c_str(), est_.Value());
}
private:
IntervalVar* const var_;
NumericalRev<int64> est_;
int64* const marker_;
};
class SetTimesBackward : public DecisionBuilder {
public:
explicit SetTimesBackward(const std::vector<IntervalVar*>& vars)
: vars_(vars), markers_(vars.size(), kint64max) {}
virtual ~SetTimesBackward() {}
virtual Decision* Next(Solver* const s) {
int64 best_end = kint64min;
int64 best_start = kint64min;
int support = -1;
int refuted = 0;
for (int i = 0; i < vars_.size(); ++i) {
IntervalVar* const v = vars_[i];
if (v->MayBePerformed() && v->EndMax() > v->EndMin()) {
if (v->EndMax() <= markers_[i] &&
(v->EndMax() > best_end ||
(v->EndMax() == best_end && v->StartMin() > best_start))) {
best_end = v->EndMax();
best_start = v->StartMin();
support = i;
} else {
refuted++;
}
}
}
// TODO(user) : remove this crude quadratic loop with
// reversibles range reduction.
if (support == -1) {
if (refuted == 0) {
return nullptr;
} else {
s->Fail();
}
}
return s->RevAlloc(new ScheduleOrExpedite(
vars_[support], vars_[support]->EndMax(), &markers_[support]));
}
virtual std::string DebugString() const { return "SetTimesBackward()"; }
virtual void Accept(ModelVisitor* const visitor) const {
visitor->BeginVisitExtension(ModelVisitor::kVariableGroupExtension);
visitor->VisitIntervalArrayArgument(ModelVisitor::kIntervalsArgument,
vars_);
visitor->EndVisitExtension(ModelVisitor::kVariableGroupExtension);
}
private:
const std::vector<IntervalVar*> vars_;
std::vector<int64> markers_;
};
// ----- Decisions and DecisionBuilders on sequences -----
class RankFirst : public Decision {
@@ -724,9 +817,25 @@ Decision* Solver::MakeScheduleOrPostpone(IntervalVar* const var, int64 est,
return RevAlloc(new ScheduleOrPostpone(var, est, marker));
}
Decision* Solver::MakeScheduleOrExpedite(IntervalVar* const var, int64 est,
int64* const marker) {
CHECK(var != nullptr);
CHECK(marker != nullptr);
return RevAlloc(new ScheduleOrExpedite(var, est, marker));
}
DecisionBuilder* Solver::MakePhase(const std::vector<IntervalVar*>& intervals,
IntervalStrategy str) {
return RevAlloc(new SetTimesForward(intervals));
switch (str) {
case Solver::INTERVAL_DEFAULT:
case Solver::INTERVAL_SIMPLE:
case Solver::INTERVAL_SET_TIMES_FORWARD:
return RevAlloc(new SetTimesForward(intervals));
case Solver::INTERVAL_SET_TIMES_BACKWARD:
return RevAlloc(new SetTimesBackward(intervals));
default:
LOG(FATAL) << "Unknown strategy " << str;
}
}
Decision* Solver::MakeRankFirstInterval(SequenceVar* const sequence,