From d493e97540ea12cebf317c6e9d86e646ab39a6e9 Mon Sep 17 00:00:00 2001 From: "lperron@google.com" Date: Fri, 17 Jan 2014 18:36:18 +0000 Subject: [PATCH] set times backward in scheduling; speedup in local search --- src/constraint_solver/constraint_solver.cc | 2 + src/constraint_solver/constraint_solver.h | 67 ++++++++-- src/constraint_solver/expr_cst.cc | 4 +- src/constraint_solver/routing.h | 31 +++-- src/constraint_solver/routing_search.cc | 144 +++++++++++++++------ src/constraint_solver/sched_search.cc | 111 +++++++++++++++- 6 files changed, 296 insertions(+), 63 deletions(-) diff --git a/src/constraint_solver/constraint_solver.cc b/src/constraint_solver/constraint_solver.cc index a593ef5dda..8c566b73df 100644 --- a/src/constraint_solver/constraint_solver.cc +++ b/src/constraint_solver/constraint_solver.cc @@ -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) {} diff --git a/src/constraint_solver/constraint_solver.h b/src/constraint_solver/constraint_solver.h index 099a8c49b4..41d42402f7 100644 --- a/src/constraint_solver/constraint_solver.h +++ b/src/constraint_solver/constraint_solver.h @@ -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& 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 elements_; diff --git a/src/constraint_solver/expr_cst.cc b/src/constraint_solver/expr_cst.cc index 5edb2f001c..7c1eae5ece 100644 --- a/src/constraint_solver/expr_cst.cc +++ b/src/constraint_solver/expr_cst.cc @@ -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; } } diff --git a/src/constraint_solver/routing.h b/src/constraint_solver/routing.h index f527c484de..72931e6684 100644 --- a/src/constraint_solver/routing.h +++ b/src/constraint_solver/routing.h @@ -382,7 +382,8 @@ class RoutingModel { typedef _RoutingModel_DisjunctionIndex DisjunctionIndex; typedef ResultCallback1 VehicleEvaluator; typedef ResultCallback2 NodeEvaluator2; - typedef std::vector > NodePairs; + typedef std::pair NodePair; + typedef std::vector 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 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* valued_positions); std::unique_ptr > 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 of the output represents an already performed node, + typedef std::pair 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 >* sorted_insertions); + void ComputeEvaluatorSortedPositions( + std::vector* 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 >* 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* 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 >* valued_positions); }; // Filtered-base decision builder based on the addition heuristic, extending diff --git a/src/constraint_solver/routing_search.cc b/src/constraint_solver/routing_search.cc index 3f2baccdaf..2e57e2cf37 100644 --- a/src/constraint_solver/routing_search.cc +++ b/src/constraint_solver/routing_search.cc @@ -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* 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 void SortAndExtractPairSeconds(std::vector>* pairs, @@ -971,12 +989,38 @@ bool GlobalCheapestInsertionFilteredDecisionBuilder::BuildSolution() { if (!InitializeRoutes()) { return false; } - // Node insertions currently being considered. - std::vector> insertions; + // Node pair insertions currently being considered. + std::vector, std::pair>> insertion_pairs; bool found = true; while (found) { found = false; - ComputeEvaluatorSortedInsertions(&insertions); + ComputeEvaluatorSortedPositionPairs(&insertion_pairs); + for (const std::pair, std::pair>& 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> insertions; + // Iterating on remaining nodes. + found = true; + while (found) { + found = false; + ComputeEvaluatorSortedPositions(&insertions); for (const std::pair insertion : insertions) { InsertBetween(insertion.second, insertion.first, Value(insertion.first)); if (Commit()) { @@ -990,30 +1034,71 @@ bool GlobalCheapestInsertionFilteredDecisionBuilder::BuildSolution() { } void GlobalCheapestInsertionFilteredDecisionBuilder:: - ComputeEvaluatorSortedInsertions( - std::vector>* sorted_insertions) { - CHECK(sorted_insertions != nullptr); - sorted_insertions->clear(); - std::vector>> valued_insertions; + ComputeEvaluatorSortedPositions( + std::vector* sorted_positions) { + CHECK(sorted_positions != nullptr); + sorted_positions->clear(); + std::vector> valued_insertions; for (int node = 0; node < model()->Size(); ++node) { if (Contains(node)) { continue; } + std::vector 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& 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>* sorted_positions) { + CHECK(sorted_positions != nullptr); + sorted_positions->clear(); + std::vector>> + 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 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 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>* 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:: diff --git a/src/constraint_solver/sched_search.cc b/src/constraint_solver/sched_search.cc index 6697bf4f5f..42ab7f91e1 100644 --- a/src/constraint_solver/sched_search.cc +++ b/src/constraint_solver/sched_search.cc @@ -380,6 +380,9 @@ void SequenceVar::FillSequence(std::vector* 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 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 est_; + int64* const marker_; +}; + +class SetTimesBackward : public DecisionBuilder { + public: + explicit SetTimesBackward(const std::vector& 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 vars_; + std::vector 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& 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,