diff --git a/constraint_solver/constraint_solver.h b/constraint_solver/constraint_solver.h index 5338430961..54d9e3eed0 100644 --- a/constraint_solver/constraint_solver.h +++ b/constraint_solver/constraint_solver.h @@ -2215,7 +2215,14 @@ class Solver { const std::vector& values); Decision* MakeFailDecision(); - // Sequential composition of Decision Builders + // Creates a decision builder which sequentially composes decision builders. + // At each leaf of a decision builder, the next decision builder is therefore + // called. For instance Compose(db1, db2) will result in the following tree: + // d1 tree | + // / | \ | + // db1 leaves | + // / | \ | + // db2 tree db2 tree db2 tree | DecisionBuilder* Compose(DecisionBuilder* const db1, DecisionBuilder* const db2); DecisionBuilder* Compose(DecisionBuilder* const db1, @@ -2227,6 +2234,34 @@ class Solver { DecisionBuilder* const db4); DecisionBuilder* Compose(const std::vector& dbs); + // Creates a decision builder which will create a search tree where each + // decision builder is called from the top of the search tree. For instance + // the decision builder Try(db1, db2) will entirely explore the search tree + // of db1 then the one of db2, resulting in the following search tree: + // Tree root | + // / \ | + // db1 tree db2 tree | + // + // This is very handy to try a decision builder which partially explores the + // search space and if it fails to try another decision builder. + // + // TODO(user): The search tree can be balanced by using binary + // "Try"-builders "recursively". For instance, Try(a,b,c,d) will give a tree + // unbalanced to the right, whereas Try(Try(a,b), Try(b,c)) will give a + // balanced tree. Investigate if we should only provide the binary version + // and/or if we should balance automatically. + DecisionBuilder* Try(DecisionBuilder* const db1, + DecisionBuilder* const db2); + DecisionBuilder* Try(DecisionBuilder* const db1, + DecisionBuilder* const db2, + DecisionBuilder* const db3); + DecisionBuilder* Try(DecisionBuilder* const db1, + DecisionBuilder* const db2, + DecisionBuilder* const db3, + DecisionBuilder* const db4); + DecisionBuilder* Try(const std::vector& dbs); + + // Phases on IntVar arrays. DecisionBuilder* MakePhase(const std::vector& vars, IntVarStrategy var_str, diff --git a/constraint_solver/local_search.cc b/constraint_solver/local_search.cc index 08db08fdf7..ee225478f4 100644 --- a/constraint_solver/local_search.cc +++ b/constraint_solver/local_search.cc @@ -50,8 +50,7 @@ namespace operations_research { // Utility methods to ensure the communication between local search and the // search. -// Returns true if a local optimum has been reached and that it cannot be -// improved. +// Returns true if a local optimum has been reached and cannot be improved. bool LocalOptimumReached(Search* const search); // Returns true if the search accepts the delta (actually checking this by diff --git a/constraint_solver/routing.cc b/constraint_solver/routing.cc index 9aa62e3b32..0c15a4681f 100644 --- a/constraint_solver/routing.cc +++ b/constraint_solver/routing.cc @@ -1151,9 +1151,133 @@ void RoutingModel::CloseModel() { cost_ = solver_->MakeSum(cost_elements)->Var(); cost_->set_name("Cost"); - SetUpSearch(); + SetupSearch(); } +// Decision builder building a solution with a single path without propagating. +// Has a very high probability of failing if the problem contains other +// constraints than path-related constraints. +// Based on an addition heuristics extending a path from its start node with +// the cheapest arc according to an evaluator. +class FastOnePathBuilder : public DecisionBuilder { + public: + // Takes ownership of evaluator. + FastOnePathBuilder(RoutingModel* const model, + ResultCallback2* evaluator) + : model_(model), evaluator_(evaluator) { + evaluator_->CheckIsRepeatable(); + } + virtual ~FastOnePathBuilder() {} + virtual Decision* Next(Solver* const solver) { + int64 index = -1; + if (!FindPathStart(&index)) { + return NULL; + } + IntVar** nexts = model_->Nexts(); + // Need to allocate in a reversible way so that if restoring the assignment + // fails, the assignment gets de-allocated. + Assignment* assignment = solver->MakeAssignment(); + int64 next = FindCheapestValue(index, *assignment); + while (next >= 0) { + assignment->Add(nexts[index]); + assignment->SetValue(nexts[index], next); + index = next; + std::vector alternates; + model_->GetDisjunctionIndicesFromIndex(index, &alternates); + for (int alternate_index = 0; + alternate_index < alternates.size(); + ++alternate_index) { + const int alternate = alternates[alternate_index]; + if (index != alternate) { + assignment->Add(nexts[alternate]); + assignment->SetValue(nexts[alternate], alternate); + } + } + next = FindCheapestValue(index, *assignment); + } + // Make unassigned nexts loop to themselves. + // TODO(user): Make finalization more robust, might have some next + // variables non-instantiated. + for (int index = 0; index < model_->Size(); ++index) { + IntVar* const next = nexts[index]; + if (!assignment->Contains(next)) { + assignment->Add(next); + if (next->Contains(index)) { + assignment->SetValue(next, index); + } + } + } + assignment->Restore(); + return NULL; + } + + private: + bool FindPathStart(int64* index) const { + IntVar** nexts = model_->Nexts(); + const int size = model_->Size(); + // Try to extend an existing path + for (int i = size - 1; i >= 0; --i) { + if (nexts[i]->Bound()) { + const int next = nexts[i]->Value(); + if (next < size && !nexts[next]->Bound()) { + *index = next; + return true; + } + } + } + // Pick path start + for (int i = size - 1; i >= 0; --i) { + if (!nexts[i]->Bound()) { + bool has_possible_prev = false; + for (int j = 0; j < size; ++j) { + if (nexts[j]->Contains(i)) { + has_possible_prev = true; + break; + } + } + if (!has_possible_prev) { + *index = i; + return true; + } + } + } + // Pick first unbound + for (int i = 0; i < size; ++i) { + if (!nexts[i]->Bound()) { + *index = i; + return true; + } + } + return false; + } + + int64 FindCheapestValue(int index, const Assignment& assignment) const { + IntVar** nexts = model_->Nexts(); + const int size = model_->Size(); + int64 best_evaluation = kint64max; + int64 best_value = -1; + if (index < size) { + IntVar* const next = nexts[index]; + scoped_ptr it(next->MakeDomainIterator(false)); + for (it->Init(); it->Ok(); it->Next()) { + const int value = it->Value(); + if (value != index + && (value >= size || !assignment.Contains(nexts[value]))) { + const int64 evaluation = evaluator_->Run(index, value); + if (evaluation <= best_evaluation) { + best_evaluation = evaluation; + best_value = value; + } + } + } + } + return best_value; + } + + RoutingModel* const model_; + scoped_ptr > evaluator_; +}; + // Decision builder to build a solution with all nodes inactive. It does no // branching and may fail if some nodes cannot be made inactive. @@ -1188,6 +1312,8 @@ RoutingModel::GetSelectedFirstSolutionStrategy() const { return ROUTING_ALL_UNPERFORMED; } else if (FLAGS_routing_first_solution.compare("BestInsertion") == 0) { return ROUTING_BEST_INSERTION; + } else if (FLAGS_routing_first_solution.compare("FastOnePath") == 0) { + return ROUTING_FAST_ONE_PATH; } return first_solution_strategy_; } @@ -1745,6 +1871,17 @@ int64 RoutingModel::NodeToIndex(NodeIndex node) const { return node_to_index_[node]; } +void RoutingModel::GetDisjunctionIndicesFromIndex(int64 index, + std::vector* indices) const { + CHECK_NOTNULL(indices); + int disjunction = -1; + if (FindCopy(node_to_disjunction_, index, &disjunction)) { + *indices = disjunctions_[disjunction].nodes; + } else { + indices->clear(); + } +} + int64 RoutingModel::GetArcCost(int64 i, int64 j, int64 vehicle) { if (vehicle < 0) { // If a node is inactive, vehicle will be set to -1; handle this situation @@ -1852,6 +1989,75 @@ void RoutingModel::CheckDepot() { } } +Assignment* RoutingModel::GetOrCreateAssignment() { + if (assignment_ == NULL) { + const int size = Size(); + assignment_ = solver_->RevAlloc(new Assignment(solver_.get())); + assignment_->Add(nexts_.get(), size); + if (!homogeneous_costs_) { + assignment_->Add(vehicle_vars_.get(), size + vehicles_); + } + assignment_->AddObjective(cost_); + } + return assignment_; +} + +SearchLimit* RoutingModel::GetOrCreateLimit() { + if (limit_ == NULL) { + limit_ = solver_->MakeLimit(time_limit_ms_, + kint64max, + kint64max, + FLAGS_routing_solution_limit, + true); + } + return limit_; +} + +SearchLimit* RoutingModel::GetOrCreateLocalSearchLimit() { + if (ls_limit_ == NULL) { + ls_limit_ = solver_->MakeLimit(time_limit_ms_, + kint64max, + kint64max, + 1, + true); + } + return ls_limit_; +} + +SearchLimit* RoutingModel::GetOrCreateLargeNeighborhoodSearchLimit() { + if (lns_limit_ == NULL) { + lns_limit_ = solver_->MakeLimit(lns_time_limit_ms_, + kint64max, + kint64max, + kint64max); + } + return lns_limit_; +} + +LocalSearchOperator* RoutingModel::CreateInsertionOperator() { + const int size = Size(); + if (pickup_delivery_pairs_.size() > 0) { + const IntVar* const* vehicle_vars = + homogeneous_costs_ ? NULL : vehicle_vars_.get(); + return MakePairActive(solver_.get(), + nexts_.get(), + vehicle_vars, + pickup_delivery_pairs_, + size); + } else { + if (homogeneous_costs_) { + return solver_->MakeOperator(nexts_.get(), + size, + Solver::MAKEACTIVE); + } else { + return solver_->MakeOperator(nexts_.get(), + vehicle_vars_.get(), + size, + Solver::MAKEACTIVE); + } + } +} + #define CP_ROUTING_PUSH_BACK_OPERATOR(operator_type) \ if (homogeneous_costs_) { \ operators.push_back(solver_->MakeOperator(nexts_.get(), \ @@ -1878,119 +2084,17 @@ void RoutingModel::CheckDepot() { operator_type)); \ } -void RoutingModel::SetUpSearch() { +LocalSearchOperator* RoutingModel::CreateNeighborhoodOperators() { const int size = Size(); - assignment_ = solver_->RevAlloc(new Assignment(solver_.get())); - assignment_->Add(nexts_.get(), size); - if (!homogeneous_costs_) { - assignment_->Add(vehicle_vars_.get(), size + vehicles_); - } - assignment_->AddObjective(cost_); - - Assignment* full_assignment = - solver_->RevAlloc(new Assignment(solver_.get())); - for (ConstIter it(cumuls_); !it.at_end(); ++it) { - full_assignment->Add(it->second, size + vehicles_); - } - for (int i = 0; i < extra_vars_.size(); ++i) { - full_assignment->Add(extra_vars_[i]); - } - full_assignment->Add(nexts_.get(), size); - full_assignment->Add(active_.get(), size); - full_assignment->Add(vehicle_vars_.get(), size + vehicles_); - full_assignment->AddObjective(cost_); - - collect_assignments_ = - solver_->MakeBestValueSolutionCollector(full_assignment, false); - monitors_.push_back(collect_assignments_); - - SearchMonitor* optimize; - switch (GetSelectedMetaheuristic()) { - case ROUTING_GUIDED_LOCAL_SEARCH: - LG << "Using Guided Local Search"; - if (homogeneous_costs_) { - optimize = solver_->MakeGuidedLocalSearch( - false, - cost_, - NewPermanentCallback(this, &RoutingModel::GetHomogeneousCost), - FLAGS_routing_optimization_step, - nexts_.get(), size, - FLAGS_routing_guided_local_search_lamda_coefficient); - } else { - optimize = solver_->MakeGuidedLocalSearch( - false, - cost_, - BuildCostCallback(), - FLAGS_routing_optimization_step, - nexts_.get(), vehicle_vars_.get(), size, - FLAGS_routing_guided_local_search_lamda_coefficient); - } - break; - case ROUTING_SIMULATED_ANNEALING: - LG << "Using Simulated Annealing"; - optimize = - solver_->MakeSimulatedAnnealing(false, cost_, - FLAGS_routing_optimization_step, - 100); - break; - case ROUTING_TABU_SEARCH: - LG << "Using Tabu Search"; - optimize = solver_->MakeTabuSearch(false, cost_, - FLAGS_routing_optimization_step, - nexts_.get(), size, - 10, 10, .8); - break; - default: - LG << "Using greedy descent"; - optimize = solver_->MakeMinimize(cost_, FLAGS_routing_optimization_step); - } - monitors_.push_back(optimize); - - limit_ = solver_->MakeLimit(time_limit_ms_, - kint64max, - kint64max, - FLAGS_routing_solution_limit, - true); - monitors_.push_back(limit_); - - ls_limit_ = solver_->MakeLimit(time_limit_ms_, - kint64max, - kint64max, - 1, - true); - - lns_limit_ = solver_->MakeLimit(lns_time_limit_ms_, - kint64max, - kint64max, - kint64max); - std::vector operators = extra_operators_; - LocalSearchOperator* insertion_operator = NULL; if (pickup_delivery_pairs_.size() > 0) { const IntVar* const* vehicle_vars = homogeneous_costs_ ? NULL : vehicle_vars_.get(); - insertion_operator = MakePairActive(solver_.get(), - nexts_.get(), - vehicle_vars, - pickup_delivery_pairs_, - size); - operators.push_back(insertion_operator); operators.push_back(MakePairRelocate(solver_.get(), nexts_.get(), vehicle_vars, pickup_delivery_pairs_, size)); - } else { - if (homogeneous_costs_) { - insertion_operator = solver_->MakeOperator(nexts_.get(), - size, - Solver::MAKEACTIVE); - } else { - insertion_operator = solver_->MakeOperator(nexts_.get(), - vehicle_vars_.get(), - size, - Solver::MAKEACTIVE); - } } if (vehicles_ > 1) { if (!FLAGS_routing_no_relocate) { @@ -2016,7 +2120,11 @@ void RoutingModel::SetUpSearch() { } if (!FLAGS_routing_no_make_active && disjunctions_.size() != 0) { CP_ROUTING_PUSH_BACK_OPERATOR(Solver::MAKEINACTIVE); - CP_ROUTING_PUSH_BACK_OPERATOR(Solver::MAKEACTIVE); + // TODO(user): On cases where we have a mix of node pairs and + // individual nodes, only pairs are going to be made active. In practice + // such cases should not appear, but we might want to be robust to them + // anyway. + operators.push_back(CreateInsertionOperator()); if (!FLAGS_routing_use_extended_swap_active) { CP_ROUTING_PUSH_BACK_OPERATOR(Solver::SWAPACTIVE); } else { @@ -2040,70 +2148,75 @@ void RoutingModel::SetUpSearch() { CP_ROUTING_PUSH_BACK_OPERATOR(Solver::UNACTIVELNS); } } + return solver_->ConcatenateOperators(operators); +} - LocalSearchOperator* local_search_operator = - solver_->ConcatenateOperators(operators); +#undef CP_ROUTING_PUSH_BACK_CALLBACK_OPERATOR +#undef CP_ROUTING_PUSH_BACK_OPERATOR - DecisionBuilder* finalize_solution = - solver_->MakePhase(nexts_.get(), size, - Solver::CHOOSE_FIRST_UNBOUND, - Solver::ASSIGN_MIN_VALUE); - - std::vector filters; - if (FLAGS_routing_use_objective_filter) { - if (homogeneous_costs_) { - LocalSearchFilter* filter = - solver_->MakeLocalSearchObjectiveFilter( - nexts_.get(), - size, - NewPermanentCallback(this, - &RoutingModel::GetHomogeneousFilterCost), - cost_, - Solver::EQ, - Solver::SUM); - filters.push_back(filter); - } else { - LocalSearchFilter* filter = - solver_->MakeLocalSearchObjectiveFilter( - nexts_.get(), - vehicle_vars_.get(), - size, - NewPermanentCallback(this, &RoutingModel::GetFilterCost), - cost_, - Solver::EQ, - Solver::SUM); - filters.push_back(filter); - } - } - if (FLAGS_routing_use_pickup_and_delivery_filter - && pickup_delivery_pairs_.size() > 0) { - filters.push_back(solver_->RevAlloc(new NodePrecedenceFilter( - nexts_.get(), - Size(), - Size() + vehicles_, - pickup_delivery_pairs_, - ""))); - } - if (FLAGS_routing_use_path_cumul_filter) { - for (ConstIter iter(cumuls_); !iter.at_end(); ++iter) { - const string& name = iter->first; - filters.push_back(solver_->RevAlloc( - new PathCumulFilter(nexts_.get(), - Size(), - iter->second, - Size() + vehicles_, - transit_evaluators_[name], - name))); +const std::vector& +RoutingModel::GetOrCreateLocalSearchFilters() { + if (filters_.empty()) { + const int size = Size(); + if (FLAGS_routing_use_objective_filter) { + if (homogeneous_costs_) { + LocalSearchFilter* filter = + solver_->MakeLocalSearchObjectiveFilter( + nexts_.get(), + size, + NewPermanentCallback(this, + &RoutingModel::GetHomogeneousFilterCost), + cost_, + Solver::EQ, + Solver::SUM); + filters_.push_back(filter); + } else { + LocalSearchFilter* filter = + solver_->MakeLocalSearchObjectiveFilter( + nexts_.get(), + vehicle_vars_.get(), + size, + NewPermanentCallback(this, &RoutingModel::GetFilterCost), + cost_, + Solver::EQ, + Solver::SUM); + filters_.push_back(filter); + } + } + if (FLAGS_routing_use_pickup_and_delivery_filter + && pickup_delivery_pairs_.size() > 0) { + filters_.push_back(solver_->RevAlloc(new NodePrecedenceFilter( + nexts_.get(), + Size(), + Size() + vehicles_, + pickup_delivery_pairs_, + ""))); + } + if (FLAGS_routing_use_path_cumul_filter) { + for (ConstIter iter(cumuls_); !iter.at_end(); ++iter) { + const string& name = iter->first; + filters_.push_back(solver_->RevAlloc( + new PathCumulFilter(nexts_.get(), + Size(), + iter->second, + Size() + vehicles_, + transit_evaluators_[name], + name))); + } } } + return filters_; +} - LocalSearchPhaseParameters* parameters = - solver_->MakeLocalSearchPhaseParameters( - local_search_operator, - solver_->MakeSolveOnce(finalize_solution, lns_limit_), - ls_limit_, - filters); +DecisionBuilder* RoutingModel::CreateSolutionFinalizer() { + return solver_->MakePhase(nexts_.get(), Size(), + Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MIN_VALUE); +} +DecisionBuilder* RoutingModel::CreateFirstSolutionDecisionBuilder() { + const int size = Size(); + DecisionBuilder* finalize_solution = CreateSolutionFinalizer(); DecisionBuilder* first_solution = finalize_solution; switch (GetSelectedFirstSolutionStrategy()) { case ROUTING_GLOBAL_CHEAPEST_ARC: @@ -2158,28 +2271,37 @@ void RoutingModel::SetUpSearch() { kint64max, true); DecisionBuilder* const finalize = - solver_->MakeSolveOnce(finalize_solution, lns_limit_); + solver_->MakeSolveOnce(finalize_solution, + GetOrCreateLargeNeighborhoodSearchLimit()); LocalSearchPhaseParameters* const insertion_parameters = solver_->MakeLocalSearchPhaseParameters( - insertion_operator, + CreateInsertionOperator(), finalize, ls_limit, - filters); + GetOrCreateLocalSearchFilters()); std::vector monitors; - monitors.push_back(limit_); + monitors.push_back(GetOrCreateLimit()); first_solution = solver_->MakeNestedOptimize( solver_->MakeLocalSearchPhase( nexts_.get(), size, solver_->RevAlloc(new AllUnperformed(this)), insertion_parameters), - assignment_, + GetOrCreateAssignment(), false, FLAGS_routing_optimization_step, monitors); first_solution = solver_->Compose(first_solution, finalize); break; } + case ROUTING_FAST_ONE_PATH: + first_solution = + solver_->RevAlloc(new FastOnePathBuilder( + this, + NewPermanentCallback( + this, + &RoutingModel::GetFirstSolutionCost))); + break; default: LOG(WARNING) << "Unknown argument for routing_first_solution, " "using default"; @@ -2189,40 +2311,129 @@ void RoutingModel::SetUpSearch() { solver_->MakeApplyBranchSelector(NewPermanentCallback(&LeftDive)); first_solution = solver_->Compose(apply, first_solution); } + return first_solution; +} - if (FLAGS_routing_dfs) { - solve_db_ = finalize_solution; +LocalSearchPhaseParameters* RoutingModel::CreateLocalSearchParameters() { + return solver_->MakeLocalSearchPhaseParameters( + CreateNeighborhoodOperators(), + solver_->MakeSolveOnce(CreateSolutionFinalizer(), + GetOrCreateLargeNeighborhoodSearchLimit()), + GetOrCreateLocalSearchLimit(), + GetOrCreateLocalSearchFilters()); +} + +DecisionBuilder* RoutingModel::CreateLocalSearchDecisionBuilder() { + const int size = Size(); + DecisionBuilder* first_solution = CreateFirstSolutionDecisionBuilder(); + LocalSearchPhaseParameters* parameters = CreateLocalSearchParameters(); + if (homogeneous_costs_) { + return solver_->MakeLocalSearchPhase(nexts_.get(), + size, + first_solution, + parameters); } else { - if (homogeneous_costs_) { - solve_db_ = solver_->MakeLocalSearchPhase(nexts_.get(), - size, - first_solution, - parameters); - } else { - const int all_size = size + size + vehicles_; - scoped_array all_vars(new IntVar*[all_size]); - for (int i = 0; i < size; ++i) { - all_vars[i] = nexts_[i]; - } - for (int i = size; i < all_size; ++i) { - all_vars[i] = vehicle_vars_[i - size]; - } - solve_db_ = solver_->MakeLocalSearchPhase(all_vars.get(), - all_size, - first_solution, - parameters); + const int all_size = size + size + vehicles_; + scoped_array all_vars(new IntVar*[all_size]); + for (int i = 0; i < size; ++i) { + all_vars[i] = nexts_[i]; } + for (int i = size; i < all_size; ++i) { + all_vars[i] = vehicle_vars_[i - size]; + } + return solver_->MakeLocalSearchPhase(all_vars.get(), + all_size, + first_solution, + parameters); } +} + +void RoutingModel::SetupDecisionBuilders() { + if (FLAGS_routing_dfs) { + solve_db_ = CreateFirstSolutionDecisionBuilder(); + } else { + solve_db_ = CreateLocalSearchDecisionBuilder(); + } + CHECK_NOTNULL(preassignment_); DecisionBuilder* restore_preassignment = solver_->MakeRestoreAssignment(preassignment_); solve_db_ = solver_->Compose(restore_preassignment, solve_db_); improve_db_ = solver_->Compose(restore_preassignment, - solver_->MakeLocalSearchPhase(assignment_, parameters)); + solver_->MakeLocalSearchPhase( + GetOrCreateAssignment(), + CreateLocalSearchParameters())); restore_assignment_ = - solver_->Compose(solver_->MakeRestoreAssignment(assignment_), - finalize_solution); + solver_->Compose(solver_->MakeRestoreAssignment(GetOrCreateAssignment()), + CreateSolutionFinalizer()); +} +void RoutingModel::SetupMetaheuristics() { + const int size = Size(); + SearchMonitor* optimize; + switch (GetSelectedMetaheuristic()) { + case ROUTING_GUIDED_LOCAL_SEARCH: + LG << "Using Guided Local Search"; + if (homogeneous_costs_) { + optimize = solver_->MakeGuidedLocalSearch( + false, + cost_, + NewPermanentCallback(this, &RoutingModel::GetHomogeneousCost), + FLAGS_routing_optimization_step, + nexts_.get(), size, + FLAGS_routing_guided_local_search_lamda_coefficient); + } else { + optimize = solver_->MakeGuidedLocalSearch( + false, + cost_, + BuildCostCallback(), + FLAGS_routing_optimization_step, + nexts_.get(), vehicle_vars_.get(), size, + FLAGS_routing_guided_local_search_lamda_coefficient); + } + break; + case ROUTING_SIMULATED_ANNEALING: + LG << "Using Simulated Annealing"; + optimize = + solver_->MakeSimulatedAnnealing(false, cost_, + FLAGS_routing_optimization_step, + 100); + break; + case ROUTING_TABU_SEARCH: + LG << "Using Tabu Search"; + optimize = solver_->MakeTabuSearch(false, cost_, + FLAGS_routing_optimization_step, + nexts_.get(), size, + 10, 10, .8); + break; + default: + LG << "Using greedy descent"; + optimize = solver_->MakeMinimize(cost_, FLAGS_routing_optimization_step); + } + monitors_.push_back(optimize); +} + +void RoutingModel::SetupAssignmentCollector() { + const int size = Size(); + Assignment* full_assignment = + solver_->RevAlloc(new Assignment(solver_.get())); + for (ConstIter it(cumuls_); !it.at_end(); ++it) { + full_assignment->Add(it->second, size + vehicles_); + } + for (int i = 0; i < extra_vars_.size(); ++i) { + full_assignment->Add(extra_vars_[i]); + } + full_assignment->Add(nexts_.get(), size); + full_assignment->Add(active_.get(), size); + full_assignment->Add(vehicle_vars_.get(), size + vehicles_); + full_assignment->AddObjective(cost_); + + collect_assignments_ = + solver_->MakeBestValueSolutionCollector(full_assignment, false); + monitors_.push_back(collect_assignments_); +} + +void RoutingModel::SetupTrace() { if (FLAGS_routing_trace) { const int kLogPeriod = 10000; SearchMonitor* trace = solver_->MakeSearchLog(kLogPeriod, cost_); @@ -2234,8 +2445,18 @@ void RoutingModel::SetUpSearch() { } } -#undef CP_ROUTING_PUSH_BACK_CALLBACK_OPERATOR -#undef CP_ROUTING_PUSH_BACK_OPERATOR +void RoutingModel::SetupSearchMonitors() { + monitors_.push_back(GetOrCreateLimit()); + SetupMetaheuristics(); + SetupAssignmentCollector(); + SetupTrace(); +} + +void RoutingModel::SetupSearch() { + SetupDecisionBuilders(); + SetupSearchMonitors(); +} + IntVar* RoutingModel::CumulVar(int64 index, const string& name) const { IntVar** named_cumuls = FindPtrOrNull(cumuls_, name); diff --git a/constraint_solver/routing.h b/constraint_solver/routing.h index 4b33670215..c36024457d 100644 --- a/constraint_solver/routing.h +++ b/constraint_solver/routing.h @@ -182,7 +182,7 @@ class RoutingModel { // Select the first node with an unbound successor and connect it to the // first available node. // This is equivalent to the CHOOSE_FIRST_UNBOUND strategy combined with - // ASSIGN_MIN_VALUE (cf. constraint_soler.h). + // ASSIGN_MIN_VALUE (cf. constraint_solver.h). ROUTING_DEFAULT_STRATEGY, // Iteratively connect two nodes which produce the cheapest route segment. ROUTING_GLOBAL_CHEAPEST_ARC, @@ -202,7 +202,13 @@ class RoutingModel { // Iteratively build a solution by inserting nodes at their cheapest (best) // position. As of 2/2012, only works on models with optional nodes // (with finite penalty costs). - ROUTING_BEST_INSERTION + ROUTING_BEST_INSERTION, + // Builds a solution with a single path without propagating. Is very fast + // but has a very high probability of failing if the problem contains other + // constraints than path-related constraints. + // Based on an addition heuristics extending a path from its start node with + // the cheapest arc according to an evaluator. + ROUTING_FAST_ONE_PATH }; // Metaheuristics used to guide the search. Apart greedy descent, they will @@ -577,6 +583,9 @@ class RoutingModel { // considered a failure case. Clients who need start and end // variable indices should use RoutingModel::Start and RoutingModel::End. int64 NodeToIndex(NodeIndex node) const; + // Returns the variable indices of the nodes in the same disjunction as the + // node corresponding to the variable of index 'index'. + void GetDisjunctionIndicesFromIndex(int64 index, std::vector* indices) const; // Time limits // Returns the current time limit used in the search. @@ -644,7 +653,26 @@ class RoutingModel { CloseModel(); } } - void SetUpSearch(); + // Sets up search objects, such as decision builders and monitors. + void SetupSearch(); + // Set of auxiliary methods used to setup the search. + // TODO(user): Document each auxiliary method. + Assignment* GetOrCreateAssignment(); + SearchLimit* GetOrCreateLimit(); + SearchLimit* GetOrCreateLocalSearchLimit(); + SearchLimit* GetOrCreateLargeNeighborhoodSearchLimit(); + LocalSearchOperator* CreateInsertionOperator(); + LocalSearchOperator* CreateNeighborhoodOperators(); + const std::vector& GetOrCreateLocalSearchFilters(); + DecisionBuilder* CreateSolutionFinalizer(); + DecisionBuilder* CreateFirstSolutionDecisionBuilder(); + LocalSearchPhaseParameters* CreateLocalSearchParameters(); + DecisionBuilder* CreateLocalSearchDecisionBuilder(); + void SetupDecisionBuilders(); + void SetupMetaheuristics(); + void SetupAssignmentCollector(); + void SetupTrace(); + void SetupSearchMonitors(); IntVar** GetOrMakeCumuls(int64 capacity, const string& name); IntVar** GetOrMakeTransits(NodeEvaluator2* evaluator, @@ -701,6 +729,7 @@ class RoutingModel { Assignment* preassignment_; std::vector extra_vars_; std::vector extra_operators_; + std::vector filters_; int64 time_limit_ms_; int64 lns_time_limit_ms_; diff --git a/constraint_solver/routing.swig b/constraint_solver/routing.swig index d92ef87f0b..d6f3f60ede 100644 --- a/constraint_solver/routing.swig +++ b/constraint_solver/routing.swig @@ -183,39 +183,33 @@ static int64 PyCallback2NodeIndexNodeIndex( } #endif #ifdef SWIGJAVA +%module(directors="1") main +%feature("director") NodeEvaluator2; %{ -class NodeIndexResultCallback2 - : public ResultCallback2 { +namespace operations_research { +class NodeEvaluator2 { public: - NodeIndexResultCallback2(ResultCallback2* callback) - : callback_(callback) { - CHECK_NOTNULL(callback_); + int64 runAux(RoutingModel::NodeIndex i, RoutingModel::NodeIndex j) { + return run(i.value(), j.value()); } - virtual ~NodeIndexResultCallback2() {} - virtual bool IsRepeatable() const { return true; } - virtual int64 Run(operations_research::RoutingModel::NodeIndex i, - operations_research::RoutingModel::NodeIndex j) { - return callback_->Run(i.value(), j.value()); + virtual int64 run(int i, int j) = 0; + RoutingModel::NodeEvaluator2* GetPermanentCallback() { + return NewPermanentCallback(this, &NodeEvaluator2::runAux); } - scoped_ptr > callback_; + virtual ~NodeEvaluator2() {} }; +} // namespace operations_research %} -%typemap(jstype) RoutingModel::NodeEvaluator2* "LongResultCallback2"; -%typemap(javain) RoutingModel::NodeEvaluator2* "SWIGTYPE_p_ResultCallback2Tlong_long_long_long_long_long_t.getCPtr($javainput.GetPermanentCallback())"; -%typemap(in) RoutingModel::NodeEvaluator2* { - if ($input) { - ResultCallback2* const callback = - (ResultCallback2*)$input; - $1 = new NodeIndexResultCallback2(callback); - } else { - SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, - "null evaluator"); - return $null; - } -} +class NodeEvaluator2 { + public: + virtual ~NodeEvaluator2(); + virtual int64 run(int i, int j) = 0; + operations_research::RoutingModel::NodeEvaluator2* GetPermanentCallback(); +}; + +%typemap(jstype) RoutingModel::NodeEvaluator2* "NodeEvaluator2"; +%typemap(javain) RoutingModel::NodeEvaluator2* "SWIGTYPE_p_ResultCallback2T_long_long__RoutingModel_NodeIndex__RoutingModel_NodeIndex_t.getCPtr($javainput.GetPermanentCallback())"; #endif // SWIGJAVA #ifdef SWIGCSHARP %module(directors="1") main diff --git a/constraint_solver/search.cc b/constraint_solver/search.cc index df16e8f0ec..760e86218f 100644 --- a/constraint_solver/search.cc +++ b/constraint_solver/search.cc @@ -365,47 +365,82 @@ SearchMonitor* Solver::MakeSearchTrace(const string& prefix) { return RevAlloc(new SearchTrace(this, prefix)); } +// ---------- Composite Decision Builder -------- + +namespace { +class CompositeDecisionBuilder : public DecisionBuilder { + public: + CompositeDecisionBuilder(); + explicit CompositeDecisionBuilder(const std::vector& dbs); + virtual ~CompositeDecisionBuilder(); + void Add(DecisionBuilder* const db); + virtual void AppendMonitors(Solver* const solver, + std::vector* const monitors); + virtual void Accept(ModelVisitor* const visitor) const; + + protected: + std::vector builders_; +}; + +CompositeDecisionBuilder::CompositeDecisionBuilder() {} + +CompositeDecisionBuilder::CompositeDecisionBuilder( + const std::vector& dbs) { + for (int i = 0; i < dbs.size(); ++i) { + Add(dbs[i]); + } +} + +CompositeDecisionBuilder::~CompositeDecisionBuilder() {} + +void CompositeDecisionBuilder::Add(DecisionBuilder* const db) { + if (db != NULL) { + builders_.push_back(db); + } +} + +void CompositeDecisionBuilder::AppendMonitors( + Solver* const solver, + std::vector* const monitors) { + for (ConstIter > it(builders_); + !it.at_end(); + ++it) { + (*it)->AppendMonitors(solver, monitors); + } +} + +void CompositeDecisionBuilder::Accept(ModelVisitor* const visitor) const { + for (ConstIter > it(builders_); + !it.at_end(); + ++it) { + (*it)->Accept(visitor); + } +} +} // namespace // ---------- Compose Decision Builder ---------- namespace { -class ComposeDecisionBuilder : public DecisionBuilder { +class ComposeDecisionBuilder : public CompositeDecisionBuilder { public: ComposeDecisionBuilder(); - explicit ComposeDecisionBuilder(const std::vector& dbs) - : start_index_(0) { - for (int i = 0; i < dbs.size(); ++i) { - Add(dbs[i]); - } - } - virtual ~ComposeDecisionBuilder() {} + explicit ComposeDecisionBuilder(const std::vector& dbs); + ~ComposeDecisionBuilder(); virtual Decision* Next(Solver* const s); virtual string DebugString() const; - void Add(DecisionBuilder* const db); - virtual void AppendMonitors(Solver* const solver, - std::vector* const monitors) { - for (ConstIter > it(builders_); - !it.at_end(); - ++it) { - (*it)->AppendMonitors(solver, monitors); - } - } - - virtual void Accept(ModelVisitor* const visitor) const { - for (ConstIter > it(builders_); - !it.at_end(); - ++it) { - (*it)->Accept(visitor); - } - } private: - std::vector builders_; int start_index_; }; ComposeDecisionBuilder::ComposeDecisionBuilder() : start_index_(0) {} +ComposeDecisionBuilder::ComposeDecisionBuilder( + const std::vector& dbs) + : CompositeDecisionBuilder(dbs), start_index_(0) {} + +ComposeDecisionBuilder::~ComposeDecisionBuilder() {} + Decision* ComposeDecisionBuilder::Next(Solver* const s) { const int size = builders_.size(); for (int i = start_index_; i < size; ++i) { @@ -424,12 +459,6 @@ string ComposeDecisionBuilder::DebugString() const { "ComposeDecisionBuilder(%s)", DebugStringArray(builders_.data(), builders_.size(), ", ").c_str()); } - -void ComposeDecisionBuilder::Add(DecisionBuilder* const db) { - if (db != NULL) { - builders_.push_back(db); - } -} } // namespace DecisionBuilder* Solver::Compose(DecisionBuilder* const db1, @@ -466,6 +495,129 @@ DecisionBuilder* Solver::Compose(const std::vector& dbs) { return RevAlloc(new ComposeDecisionBuilder(dbs)); } +// ---------- Try Decision Builder ---------- + +namespace { + +class TryDecisionBuilder; + +class TryDecision : public Decision { + public: + explicit TryDecision(TryDecisionBuilder* const try_builder); + virtual ~TryDecision(); + virtual void Apply(Solver* const solver); + virtual void Refute(Solver* const solver); + virtual string DebugString() const { + return "TryDecision"; + } + + private: + TryDecisionBuilder* const try_builder_; +}; + +class TryDecisionBuilder : public CompositeDecisionBuilder { + public: + TryDecisionBuilder(); + explicit TryDecisionBuilder(const std::vector& dbs); + virtual ~TryDecisionBuilder(); + virtual Decision* Next(Solver* const s); + virtual string DebugString() const; + void AdvanceToNextBuilder(Solver* const solver); + + private: + TryDecision try_decision_; + int current_builder_; + bool start_new_builder_; +}; + +TryDecision::TryDecision(TryDecisionBuilder* const try_builder) + : try_builder_(try_builder) {} + +TryDecision::~TryDecision() {} + +void TryDecision::Apply(Solver* const solver) {} + +void TryDecision::Refute(Solver* const solver) { + try_builder_->AdvanceToNextBuilder(solver); +} + +TryDecisionBuilder::TryDecisionBuilder() + : CompositeDecisionBuilder(), + try_decision_(this), + current_builder_(-1), + start_new_builder_(true) {} + +TryDecisionBuilder::TryDecisionBuilder(const std::vector& dbs) + : CompositeDecisionBuilder(dbs), + try_decision_(this), + current_builder_(-1), + start_new_builder_(true) {} + +TryDecisionBuilder::~TryDecisionBuilder() {} + +Decision* TryDecisionBuilder::Next(Solver* const solver) { + if (current_builder_ < 0) { + solver->SaveAndSetValue(¤t_builder_, 0); + start_new_builder_ = true; + } + if (start_new_builder_) { + start_new_builder_ = false; + return &try_decision_; + } else { + return builders_[current_builder_]->Next(solver); + } +} + +string TryDecisionBuilder::DebugString() const { + return StringPrintf( + "TryDecisionBuilder(%s)", + DebugStringArray(builders_.data(), builders_.size(), ", ").c_str()); +} + +void TryDecisionBuilder::AdvanceToNextBuilder(Solver* const solver) { + ++current_builder_; + start_new_builder_ = true; + if (current_builder_ >= builders_.size()) { + solver->Fail(); + } +} + +} // namespace + +DecisionBuilder* Solver::Try(DecisionBuilder* const db1, + DecisionBuilder* const db2) { + TryDecisionBuilder* try_db = RevAlloc(new TryDecisionBuilder()); + try_db->Add(db1); + try_db->Add(db2); + return try_db; +} + +DecisionBuilder* Solver::Try(DecisionBuilder* const db1, + DecisionBuilder* const db2, + DecisionBuilder* const db3) { + TryDecisionBuilder* try_db = RevAlloc(new TryDecisionBuilder()); + try_db->Add(db1); + try_db->Add(db2); + try_db->Add(db3); + return try_db; +} + +DecisionBuilder* Solver::Try(DecisionBuilder* const db1, + DecisionBuilder* const db2, + DecisionBuilder* const db3, + DecisionBuilder* const db4) { + TryDecisionBuilder* try_db = RevAlloc(new TryDecisionBuilder()); + try_db->Add(db1); + try_db->Add(db2); + try_db->Add(db3); + try_db->Add(db4); + return try_db; +} + +DecisionBuilder* Solver::Try(const std::vector& dbs) { + return RevAlloc(new TryDecisionBuilder(dbs)); +} + // ---------- Variable Assignments ---------- // ----- BaseAssignmentSelector -----