Files
ortools-clone/constraint_solver/routing.cc
2011-06-23 08:48:05 +00:00

1414 lines
47 KiB
C++

// Copyright 2010-2011 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.
#include "constraint_solver/routing.h"
#include <algorithm>
#include "base/hash.h"
#include "base/callback.h"
#include "base/commandlineflags.h"
#include "base/integral_types.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/timer.h"
#include "base/concise_iterator.h"
#include "base/map-util.h"
#include "base/stl_util-inl.h"
#include "base/hash.h"
#include "constraint_solver/constraint_solveri.h"
// Neighborhood deactivation
DEFINE_bool(routing_no_lns, false,
"Routing: forbids use of Large Neighborhood Search.");
DEFINE_bool(routing_no_relocate, false,
"Routing: forbids use of Relocate neighborhood.");
DEFINE_bool(routing_no_exchange, false,
"Routing: forbids use of Exchange neighborhood.");
DEFINE_bool(routing_no_cross, false,
"Routing: forbids use of Cross neighborhood.");
DEFINE_bool(routing_no_2opt, false,
"Routing: forbids use of 2Opt neighborhood.");
DEFINE_bool(routing_no_oropt, false,
"Routing: forbids use of OrOpt neighborhood.");
DEFINE_bool(routing_no_make_active, false,
"Routing: forbids use of MakeActive/SwapActive/MakeInactive "
"neighborhoods.");
DEFINE_bool(routing_no_lkh, false,
"Routing: forbids use of LKH neighborhood.");
DEFINE_bool(routing_no_tsp, true,
"Routing: forbids use of TSPOpt neighborhood.");
DEFINE_bool(routing_no_tsplns, true,
"Routing: forbids use of TSPLNS neighborhood.");
DEFINE_bool(routing_use_extended_swap_active, false,
"Routing: use extended version of SwapActive neighborhood.");
// Search limits
DEFINE_int64(routing_solution_limit, kint64max,
"Routing: number of solutions limit.");
DEFINE_int64(routing_time_limit, kint64max,
"Routing: time limit in ms.");
DEFINE_int64(routing_lns_time_limit, 100,
"Routing: time limit in ms for LNS sub-decisionbuilder.");
// Meta-heuritics
DEFINE_bool(routing_guided_local_search, false, "Routing: use GLS.");
DEFINE_double(routing_guided_local_search_lamda_coefficient, 0.1,
"Lamda coefficient in GLS.");
DEFINE_bool(routing_simulated_annealing, false,
"Routing: use simulated annealing.");
DEFINE_bool(routing_tabu_search, false, "Routing: use tabu search.");
// Search control
DEFINE_bool(routing_dfs, false,
"Routing: use a complete deoth-first search.");
DEFINE_string(routing_first_solution, "",
"Routing: first solution heuristic;possible values are "
"Default, GlobalCheapestArc, LocalCheapestArc, PathCheapestArc.");
DEFINE_bool(routing_use_first_solution_dive, false,
"Dive (left-branch) for first solution.");
DEFINE_int64(routing_optimization_step, 1, "Optimization step.");
// Filtering control
DEFINE_bool(routing_use_objective_filter, true,
"Use objective filter to speed up local search.");
DEFINE_bool(routing_use_path_cumul_filter, true,
"Use PathCumul constraint filter to speed up local search.");
// Misc
DEFINE_bool(routing_cache_callbacks, false, "Cache callback calls.");
DEFINE_int64(routing_max_cache_size, 1000,
"Maximum cache size when callback caching is on.");
DEFINE_bool(routing_trace, false, "Routing: trace search.");
DEFINE_bool(routing_use_homogeneous_costs, true,
"Routing: use homogeneous cost model when possible.");
namespace operations_research {
// Cached callbacks
class RoutingCache {
public:
RoutingCache(Solver::IndexEvaluator2* callback, int size) :
cache_(size), callback_(callback) {
callback->CheckIsRepeatable();
}
int64 Run(int64 i, int64 j) {
// This method does lazy caching of results of callbacks: first
// checks if it has been run with these parameters before, and
// returns previous result if so, or runs underlaying callback and
// stores its result.
int64 cached_value = 0;
if (!FindCopy(cache_[i], j, &cached_value)) {
cached_value = callback_->Run(i, j);
cache_[i][j] = cached_value;
}
return cached_value;
}
private:
std::vector<hash_map<int, int64> > cache_;
scoped_ptr<Solver::IndexEvaluator2> callback_;
};
namespace {
// PathCumul filter
// TODO(user): Move this to local_search.cc.
class PathCumulFilter : public IntVarLocalSearchFilter {
public:
// Does not take ownership of evaluator.
PathCumulFilter(const IntVar* const* nexts,
int nexts_size,
const IntVar* const* cumuls,
int cumuls_size,
Solver::IndexEvaluator2* evaluator,
const string& name);
virtual ~PathCumulFilter() {}
virtual bool Accept(const Assignment* delta,
const Assignment* deltadelta);
virtual void OnSynchronize();
private:
scoped_array<IntVar*> cumuls_;
const int cumuls_size_;
std::vector<int64> saved_nexts_;
std::vector<int64> node_path_starts_;
Solver::IndexEvaluator2* const evaluator_;
const string name_;
static const int64 kUnassigned;
};
const int64 PathCumulFilter::kUnassigned = -1;
PathCumulFilter::PathCumulFilter(const IntVar* const* nexts,
int nexts_size,
const IntVar* const* cumuls,
int cumuls_size,
Solver::IndexEvaluator2* evaluator,
const string& name)
: IntVarLocalSearchFilter(nexts, nexts_size),
cumuls_size_(cumuls_size),
saved_nexts_(nexts_size),
node_path_starts_(cumuls_size),
evaluator_(evaluator),
name_(name) {
cumuls_.reset(new IntVar*[cumuls_size_]);
memcpy(cumuls_.get(), cumuls, cumuls_size_ * sizeof(*cumuls));
}
// Complexity: O(Sum(Length(paths modified)) + #paths modified ^ 2).
// (#paths modified is usually very small).
bool PathCumulFilter::Accept(const Assignment* delta,
const Assignment* deltadelta) {
const Assignment::IntContainer& container = delta->IntVarContainer();
const int delta_size = container.Size();
// Determining touched paths.
std::vector<int64> touched_paths;
for (int i = 0; i < delta_size; ++i) {
const IntVarElement& new_element = container.Element(i);
const IntVar* const var = new_element.Var();
int64 index = kUnassigned;
if (FindIndex(var, &index)) {
const int64 start = node_path_starts_[index];
if (start != kUnassigned
&& find(touched_paths.begin(), touched_paths.end(), start)
== touched_paths.end()) {
touched_paths.push_back(start);
}
}
}
// Checking feasibility of touched paths.
const int touched_paths_size = touched_paths.size();
for (int i = 0; i < touched_paths_size; ++i) {
int64 node = touched_paths[i];
int64 cumul = cumuls_[node]->Min();
while (node < Size()) {
const IntVar* const next_var = Var(node);
int64 next = saved_nexts_[node];
if (container.Contains(next_var)) {
const IntVarElement& element = container.Element(next_var);
if (element.Bound()) {
next = element.Value();
} else {
// LNS detected, return true since path was ok up to now.
return true;
}
}
cumul += evaluator_->Run(node, next);
if (cumul > cumuls_[next]->Max()) {
return false;
}
cumul = std::max(cumuls_[next]->Min(), cumul);
node = next;
}
}
return true;
}
void PathCumulFilter::OnSynchronize() {
const int nexts_size = Size();
// Detecting path starts, used to track which node belongs to which path.
std::vector<int64> path_starts;
Bitmap has_prevs(nexts_size, false);
for (int i = 0; i < nexts_size; ++i) {
const int next = Value(i);
if (next < nexts_size) {
has_prevs.Set(next, true);
}
}
for (int i = 0; i < nexts_size; ++i) {
if (!has_prevs.Get(i)) {
path_starts.push_back(i);
}
}
// Marking unactive nodes (which are not on a path).
node_path_starts_.assign(cumuls_size_, kUnassigned);
// Marking nodes on a path and storing next values.
for (int i = 0; i < path_starts.size(); ++i) {
const int64 start = path_starts[i];
int node = start;
node_path_starts_[node] = start;
int next = Value(node);
saved_nexts_[node] = next;
while (next < nexts_size) {
node = next;
node_path_starts_[node] = start;
next = Value(node);
saved_nexts_[node] = next;
}
saved_nexts_[node] = next;
node_path_starts_[next] = start;
}
}
// Evaluators
int64 CostFunction(int64** eval, int64 i, int64 j) {
return eval[i][j];
}
class MatrixEvaluator : public BaseObject {
public:
MatrixEvaluator(const int64* const* values,
int nodes,
RoutingModel* model)
: values_(new int64*[nodes]), nodes_(nodes), model_(model) {
CHECK(values) << "null pointer";
for (int i = 0; i < nodes_; ++i) {
values_[i] = new int64[nodes_];
memcpy(values_[i], values[i], nodes_ * sizeof(*values[i]));
}
}
virtual ~MatrixEvaluator() {
for (int i = 0; i < nodes_; ++i) {
delete values_[i];
}
delete [] values_;
}
int64 Value(int64 i, int64 j) const {
return values_[model_->IndexToNode(i)][model_->IndexToNode(j)];
}
private:
int64** const values_;
const int nodes_;
RoutingModel* const model_;
};
class VectorEvaluator : public BaseObject {
public:
VectorEvaluator(const int64* values, int64 nodes, RoutingModel* model)
: values_(new int64[nodes]), nodes_(nodes), model_(model) {
CHECK(values) << "null pointer";
memcpy(values_.get(), values, nodes * sizeof(*values));
}
virtual ~VectorEvaluator() {}
int64 Value(int64 i, int64 j) const;
private:
scoped_array<int64> values_;
const int64 nodes_;
RoutingModel* const model_;
};
int64 VectorEvaluator::Value(int64 i, int64 j) const {
const int64 index = model_->IndexToNode(i);
return values_[index];
}
class ConstantEvaluator : public BaseObject {
public:
explicit ConstantEvaluator(int64 value) : value_(value) {}
virtual ~ConstantEvaluator() {}
int64 Value(int64 i, int64 j) const { return value_; }
private:
const int64 value_;
};
// Left-branch dive branch selector
Solver::DecisionModification LeftDive(Solver* const s) {
return Solver::KEEP_LEFT;
}
} // namespace
// ----- Routing model -----
static const int kUnassigned = -1;
static const int64 kNoPenalty = -1;
RoutingModel::RoutingModel(int nodes, int vehicles)
: solver_(NULL),
no_cycle_constraint_(NULL),
costs_(vehicles),
homogeneous_costs_(FLAGS_routing_use_homogeneous_costs),
cost_(0),
fixed_costs_(vehicles),
nodes_(nodes),
vehicles_(vehicles),
starts_(vehicles),
ends_(vehicles),
start_end_count_(1),
is_depot_set_(false),
closed_(false),
first_solution_strategy_(ROUTING_DEFAULT_STRATEGY),
first_solution_evaluator_(NULL),
metaheuristic_(ROUTING_GREEDY_DESCENT),
collect_assignments_(NULL),
solve_db_(NULL),
improve_db_(NULL),
restore_assignment_(NULL),
assignment_(NULL),
preassignment_(NULL),
time_limit_ms_(FLAGS_routing_time_limit),
lns_time_limit_ms_(FLAGS_routing_lns_time_limit),
limit_(NULL),
ls_limit_(NULL),
lns_limit_(NULL) {
SolverParameters parameters;
solver_.reset(new Solver("Routing", parameters));
Initialize();
}
RoutingModel::RoutingModel(int nodes,
int vehicles,
const std::vector<std::pair<int, int> >& start_end)
: solver_(NULL),
no_cycle_constraint_(NULL),
costs_(vehicles),
homogeneous_costs_(FLAGS_routing_use_homogeneous_costs),
fixed_costs_(vehicles),
nodes_(nodes),
vehicles_(vehicles),
starts_(vehicles),
ends_(vehicles),
is_depot_set_(false),
closed_(false),
first_solution_strategy_(ROUTING_DEFAULT_STRATEGY),
first_solution_evaluator_(NULL),
metaheuristic_(ROUTING_GREEDY_DESCENT),
collect_assignments_(NULL),
solve_db_(NULL),
improve_db_(NULL),
restore_assignment_(NULL),
assignment_(NULL),
preassignment_(NULL),
time_limit_ms_(FLAGS_routing_time_limit),
lns_time_limit_ms_(FLAGS_routing_lns_time_limit),
limit_(NULL),
ls_limit_(NULL),
lns_limit_(NULL) {
SolverParameters parameters;
solver_.reset(new Solver("Routing", parameters));
CHECK_EQ(vehicles, start_end.size());
hash_set<int> depot_set;
for (int i = 0; i < start_end.size(); ++i) {
depot_set.insert(start_end[i].first);
depot_set.insert(start_end[i].second);
}
start_end_count_ = depot_set.size();
Initialize();
SetStartEnd(start_end);
}
RoutingModel::RoutingModel(int nodes,
int vehicles,
const std::vector<int>& starts,
const std::vector<int>& ends)
: solver_(NULL),
no_cycle_constraint_(NULL),
costs_(vehicles),
homogeneous_costs_(FLAGS_routing_use_homogeneous_costs),
fixed_costs_(vehicles),
nodes_(nodes),
vehicles_(vehicles),
starts_(vehicles),
ends_(vehicles),
is_depot_set_(false),
closed_(false),
first_solution_strategy_(ROUTING_DEFAULT_STRATEGY),
first_solution_evaluator_(NULL),
metaheuristic_(ROUTING_GREEDY_DESCENT),
collect_assignments_(NULL),
solve_db_(NULL),
improve_db_(NULL),
restore_assignment_(NULL),
assignment_(NULL),
preassignment_(NULL),
time_limit_ms_(FLAGS_routing_time_limit),
lns_time_limit_ms_(FLAGS_routing_lns_time_limit),
limit_(NULL),
ls_limit_(NULL),
lns_limit_(NULL) {
SolverParameters parameters;
solver_.reset(new Solver("Routing", parameters));
CHECK_EQ(vehicles, starts.size());
CHECK_EQ(vehicles, ends.size());
hash_set<int> depot_set;
std::vector<std::pair<int, int> > start_end(starts.size());
for (int i = 0; i < starts.size(); ++i) {
depot_set.insert(starts[i]);
depot_set.insert(ends[i]);
start_end[i] = std::make_pair(starts[i], ends[i]);
}
start_end_count_ = depot_set.size();
Initialize();
SetStartEnd(start_end);
}
void RoutingModel::Initialize() {
const int size = Size();
// Next variables
nexts_.reset(solver_->MakeIntVarArray(size,
0,
size + vehicles_ - 1,
"Nexts"));
solver_->AddConstraint(solver_->MakeAllDifferent(nexts_.get(), size, false));
// Vehicle variables
vehicle_vars_.reset(solver_->MakeIntVarArray(size + vehicles_,
0,
vehicles_ - 1,
"Vehicles"));
// Active variables
active_.reset(solver_->MakeBoolVarArray(size, "Active"));
// Cost cache
cost_cache_.clear();
cost_cache_.resize(size);
for (int i = 0; i < size; ++i) {
CostCacheElement& cache = cost_cache_[i];
cache.node = kUnassigned;
cache.vehicle = kUnassigned;
cache.cost = 0;
}
preassignment_ = solver_->RevAlloc(new Assignment(solver_.get()));
}
RoutingModel::~RoutingModel() {
STLDeleteElements(&routing_caches_);
STLDeleteElements(&owned_callbacks_);
}
void RoutingModel::AddNoCycleConstraint() {
AddNoCycleConstraintInternal();
}
void RoutingModel::AddNoCycleConstraintInternal() {
CheckDepot();
if (no_cycle_constraint_ == NULL) {
no_cycle_constraint_ = solver_->MakeNoCycle(nexts_.get(),
active_.get(),
Size());
solver_->AddConstraint(no_cycle_constraint_);
}
}
void RoutingModel::AddDimension(Solver::IndexEvaluator2* evaluator,
int64 slack_max,
int64 capacity,
const string& name) {
CheckDepot();
IntVar** cumuls = GetOrMakeCumuls(capacity, name);
const int size = Size();
IntVar** transits = GetOrMakeTransits(NewCachedCallback(evaluator),
slack_max, capacity, name);
solver_->AddConstraint(solver_->MakePathCumul(nexts_.get(),
active_.get(),
cumuls,
transits,
size,
size + vehicles_));
// Start cumuls == 0
for (int i = 0; i < vehicles_; ++i) {
solver_->AddConstraint(solver_->MakeEquality(cumuls[Start(i)], Zero()));
}
}
void RoutingModel::AddConstantDimension(int64 value,
int64 capacity,
const string& name) {
ConstantEvaluator* evaluator =
solver_->RevAlloc(new ConstantEvaluator(value));
AddDimension(NewPermanentCallback(evaluator, &ConstantEvaluator::Value),
0, capacity, name);
}
void RoutingModel::AddVectorDimension(const int64* values,
int64 capacity,
const string& name) {
VectorEvaluator* evaluator =
solver_->RevAlloc(new VectorEvaluator(values, nodes_, this));
AddDimension(NewPermanentCallback(evaluator, &VectorEvaluator::Value),
0, capacity, name);
}
void RoutingModel::AddMatrixDimension(const int64* const* values,
int64 capacity,
const string& name) {
MatrixEvaluator* evaluator =
solver_->RevAlloc(new MatrixEvaluator(values, nodes_, this));
AddDimension(NewPermanentCallback(evaluator, &MatrixEvaluator::Value),
0, capacity, name);
}
void RoutingModel::AddAllActive() {
for (int i = 0; i < Size(); ++i) {
if (active_[i]->Max() != 0) {
active_[i]->SetValue(1);
}
}
}
void RoutingModel::SetCost(Solver::IndexEvaluator2* evaluator) {
evaluator->CheckIsRepeatable();
Solver::IndexEvaluator2* cached_evaluator = NewCachedCallback(evaluator);
homogeneous_costs_ = FLAGS_routing_use_homogeneous_costs;
for (int i = 0; i < vehicles_; ++i) {
SetVehicleCostInternal(i, cached_evaluator);
}
}
int64 RoutingModel::GetRouteFixedCost() const {
return GetVehicleFixedCost(0);
}
void RoutingModel::SetVehicleCost(int vehicle,
Solver::IndexEvaluator2* evaluator) {
evaluator->CheckIsRepeatable();
homogeneous_costs_ = false;
SetVehicleCostInternal(vehicle, NewCachedCallback(evaluator));
}
void RoutingModel::SetVehicleCostInternal(int vehicle,
Solver::IndexEvaluator2* evaluator) {
CHECK_LT(vehicle, vehicles_);
costs_[vehicle] = evaluator;
}
void RoutingModel::SetRouteFixedCost(int64 cost) {
for (int i = 0; i < vehicles_; ++i) {
SetVehicleFixedCost(i, cost);
}
}
int64 RoutingModel::GetVehicleFixedCost(int vehicle) const {
CHECK_LT(vehicle, vehicles_);
return fixed_costs_[vehicle];
}
void RoutingModel::SetVehicleFixedCost(int vehicle, int64 cost) {
CHECK_LT(vehicle, vehicles_);
fixed_costs_[vehicle] = cost;
}
void RoutingModel::AddDisjunction(const std::vector<int64>& nodes) {
AddDisjunctionInternal(nodes, kNoPenalty);
}
void RoutingModel::AddDisjunction(const std::vector<int64>& nodes, int64 penalty) {
CHECK_GE(penalty, 0) << "Penalty must be positive";
AddDisjunctionInternal(nodes, penalty);
}
void RoutingModel::AddDisjunctionInternal(const std::vector<int64>& nodes,
int64 penalty) {
const int size = disjunctions_.size();
disjunctions_.resize(size + 1);
std::vector<int64>& disjunction_nodes = disjunctions_[size].nodes;
disjunction_nodes.resize(nodes.size());
for (int i = 0; i < nodes.size(); ++i) {
CHECK_NE(kUnassigned, node_to_index_[nodes[i]]);
disjunction_nodes[i] = node_to_index_[nodes[i]];
}
disjunctions_[size].penalty = penalty;
for (int i = 0; i < nodes.size(); ++i) {
// TODO(user): support multiple disjunction per node
node_to_disjunction_[node_to_index_[nodes[i]]] = size;
}
}
IntVar* RoutingModel::CreateDisjunction(int disjunction) {
const std::vector<int64>& nodes = disjunctions_[disjunction].nodes;
const int nodes_size = nodes.size();
std::vector<IntVar*> disjunction_vars(nodes_size + 1);
for (int i = 0; i < nodes_size; ++i) {
const int64 node = nodes[i];
CHECK_LT(node, Size());
disjunction_vars[i] = ActiveVar(node);
}
IntVar* no_active_var = solver_->MakeBoolVar();
disjunction_vars[nodes_size] = no_active_var;
solver_->AddConstraint(solver_->MakeSumEquality(disjunction_vars, 1));
const int64 penalty = disjunctions_[disjunction].penalty;
if (penalty < 0) {
no_active_var->SetMax(0);
return NULL;
} else {
return solver_->MakeProd(no_active_var, penalty)->Var();
}
}
void RoutingModel::AddLocalSearchOperator(LocalSearchOperator* ls_operator) {
extra_operators_.push_back(ls_operator);
}
void RoutingModel::SetDepot(int depot) {
std::vector<std::pair<int, int> > start_end(vehicles_, std::make_pair(depot, depot));
SetStartEnd(start_end);
}
void RoutingModel::SetStartEnd(const std::vector<std::pair<int, int> >& start_end) {
if (is_depot_set_) {
LOG(WARNING) << "A depot has already been specified, ignoring new ones";
return;
}
CHECK_EQ(start_end.size(), vehicles_);
const int size = Size();
hash_set<int> starts;
hash_set<int> ends;
for (int i = 0; i < vehicles_; ++i) {
int start = start_end[i].first;
int end = start_end[i].second;
CHECK_GE(start, 0);
CHECK_GE(end, 0);
CHECK_LE(start, nodes_);
CHECK_LE(end, nodes_);
starts.insert(start);
ends.insert(end);
}
index_to_node_.resize(size + vehicles_);
node_to_index_.resize(nodes_, kUnassigned);
int index = 0;
for (int i = 0; i < nodes_; ++i) {
if (starts.count(i) != 0 || ends.count(i) == 0) {
index_to_node_[index] = i;
node_to_index_[i] = index;
++index;
}
}
hash_set<int> node_set;
index_to_vehicle_.resize(size + vehicles_, kUnassigned);
for (int i = 0; i < vehicles_; ++i) {
int start = start_end[i].first;
if (node_set.count(start) == 0) {
node_set.insert(start);
const int start_index = node_to_index_[start];
starts_[i] = start_index;
CHECK_NE(kUnassigned, start_index);
index_to_vehicle_[start_index] = i;
} else {
starts_[i] = index;
index_to_node_[index] = start;
index_to_vehicle_[index] = i;
++index;
}
}
for (int i = 0; i < vehicles_; ++i) {
int end = start_end[i].second;
index_to_node_[index] = end;
ends_[i] = index;
CHECK_LE(size, index);
index_to_vehicle_[index] = i;
++index;
}
for (int i = 0; i < size; ++i) {
for (int j = 0; j < vehicles_; ++j) {
// "start" node: nexts_[i] != start
solver_->AddConstraint(solver_->MakeNonEquality(nexts_[i], starts_[j]));
}
// Extra constraint to state a node can't point to itself
solver_->AddConstraint(solver_->MakeIsDifferentCstCt(nexts_[i],
i,
active_[i]));
}
is_depot_set_ = true;
// Logging model information.
VLOG(1) << "Number of nodes: " << nodes_;
VLOG(1) << "Number of vehicles: " << vehicles_;
for (int index = 0; index < index_to_node_.size(); ++index) {
VLOG(1) << "Variable index " << index
<< " -> Node index " << index_to_node_[index];
}
for (int node = 0; node < node_to_index_.size(); ++node) {
VLOG(2) << "Node index " << node
<< " -> Variable index " << node_to_index_[node];
}
}
void RoutingModel::CloseModel() {
if (closed_) {
LOG(WARNING) << "Model already closed";
return;
}
closed_ = true;
CheckDepot();
AddNoCycleConstraintInternal();
const int size = Size();
// Vehicle variable constraints
for (int i = 0; i < vehicles_; ++i) {
solver_->AddConstraint(solver_->MakeEquality(
vehicle_vars_[starts_[i]], solver_->MakeIntConst(i)));
solver_->AddConstraint(solver_->MakeEquality(
vehicle_vars_[ends_[i]], solver_->MakeIntConst(i)));
}
std::vector<IntVar*> zero_transit(size, solver_->MakeIntConst(Zero()));
solver_->AddConstraint(solver_->MakePathCumul(nexts_.get(),
active_.get(),
vehicle_vars_.get(),
zero_transit.data(),
size,
size + vehicles_));
// Set all active unless there are disjunctions
if (disjunctions_.size() == 0) {
AddAllActive();
}
// Associate first and "logical" last nodes
for (int i = 0; i < vehicles_; ++i) {
for (int j = 0; j < vehicles_; ++j) {
if (i != j) {
nexts_[starts_[i]]->RemoveValue(ends_[j]);
}
}
}
std::vector<IntVar*> cost_elements;
// Arc costs: the cost of an arc (i, nexts_[i], vehicle_vars_[i]) is
// costs_(nexts_[i], vehicle_vars_[i]); the total cost is the sum of arc
// costs.
for (int i = 0; i < size; ++i) {
IntExpr* expr = NULL;
if (homogeneous_costs_) {
expr = solver_->MakeElement(
NewPermanentCallback(this,
&RoutingModel::GetHomogeneousCost,
static_cast<int64>(i)),
nexts_[i]);
} else {
expr = solver_->MakeElement(
NewPermanentCallback(this,
&RoutingModel::GetCost,
static_cast<int64>(i)),
nexts_[i],
vehicle_vars_[i]);
}
IntVar* var = solver_->MakeProd(expr, active_[i])->Var();
cost_elements.push_back(var);
}
// Penalty costs
for (int i = 0; i < disjunctions_.size(); ++i) {
IntVar* penalty_var = CreateDisjunction(i);
if (penalty_var != NULL) {
cost_elements.push_back(penalty_var);
}
}
cost_ = solver_->MakeSum(cost_elements)->Var();
cost_->set_name("Cost");
SetUpSearch();
}
// Flags override strategy selection
RoutingModel::RoutingStrategy
RoutingModel::GetSelectedFirstSolutionStrategy() const {
if (FLAGS_routing_first_solution.compare("GlobalCheapestArc") == 0) {
return ROUTING_GLOBAL_CHEAPEST_ARC;
} else if (FLAGS_routing_first_solution.compare("LocalCheapestArc") == 0) {
return ROUTING_LOCAL_CHEAPEST_ARC;
} else if (FLAGS_routing_first_solution.compare("PathCheapestArc") == 0) {
return ROUTING_PATH_CHEAPEST_ARC;
}
return first_solution_strategy_;
}
RoutingModel::RoutingMetaheuristic
RoutingModel::GetSelectedMetaheuristic() const {
if (FLAGS_routing_tabu_search) {
return ROUTING_TABU_SEARCH;
} else if (FLAGS_routing_simulated_annealing) {
return ROUTING_SIMULATED_ANNEALING;
} else if (FLAGS_routing_guided_local_search) {
return ROUTING_GUIDED_LOCAL_SEARCH;
}
return metaheuristic_;
}
void RoutingModel::AddSearchMonitor(SearchMonitor* const monitor) {
monitors_.push_back(monitor);
}
const Assignment* RoutingModel::Solve(const Assignment* assignment) {
if (!closed_) {
CloseModel();
}
if (NULL == assignment) {
solver_->Solve(solve_db_, monitors_);
} else {
assignment_->Copy(assignment);
solver_->Solve(improve_db_, monitors_);
}
if (collect_assignments_->solution_count() == 1) {
return collect_assignments_->solution(0);
} else {
return NULL;
}
}
int RoutingModel::FindNextActive(int index, const std::vector<int>& nodes) const {
++index;
CHECK_LE(0, index);
const int size = nodes.size();
while (index < size && ActiveVar(nodes[index])->Max() == 0) {
++index;
}
return index;
}
IntVar* RoutingModel::ApplyLocks(const std::vector<int>& locks) {
preassignment_->Clear();
IntVar* next_var = NULL;
int lock_index = FindNextActive(-1, locks);
const int size = locks.size();
if (lock_index < size) {
next_var = NextVar(locks[lock_index]);
preassignment_->Add(next_var);
for (lock_index = FindNextActive(lock_index, locks);
lock_index < size;
lock_index = FindNextActive(lock_index, locks)) {
preassignment_->SetValue(next_var, locks[lock_index]);
next_var = NextVar(locks[lock_index]);
preassignment_->Add(next_var);
}
}
return next_var;
}
void RoutingModel::UpdateTimeLimit(int64 limit_ms) {
time_limit_ms_ = limit_ms;
if (limit_ != NULL) {
solver_->UpdateLimits(time_limit_ms_,
kint64max,
kint64max,
FLAGS_routing_solution_limit,
limit_);
}
if (ls_limit_ != NULL) {
solver_->UpdateLimits(time_limit_ms_,
kint64max,
kint64max,
1,
ls_limit_);
}
}
void RoutingModel::UpdateLNSTimeLimit(int64 limit_ms) {
lns_time_limit_ms_ = limit_ms;
if (lns_limit_ != NULL) {
solver_->UpdateLimits(lns_time_limit_ms_,
kint64max,
kint64max,
kint64max,
lns_limit_);
}
}
void RoutingModel::SetCommandLineOption(const string& name,
const string& value) {
google::SetCommandLineOption(name.c_str(), value.c_str());
}
int64 RoutingModel::IndexToNode(int64 index) const {
DCHECK_LT(index, index_to_node_.size());
return index_to_node_[index];
}
int64 RoutingModel::NodeToIndex(int64 node) const {
DCHECK_LT(node, node_to_index_.size());
DCHECK_NE(node_to_index_[node], kUnassigned);
return node_to_index_[node];
}
int64 RoutingModel::GetArcCost(int64 i, int64 j, int64 vehicle) {
CostCacheElement& cache = cost_cache_[i];
if (cache.node == j && cache.vehicle == vehicle) {
return cache.cost;
}
const int64 node_i = IndexToNode(i);
const int64 node_j = IndexToNode(j);
int64 cost = 0;
if (!IsStart(i)) {
cost = costs_[vehicle]->Run(node_i, node_j);
} else if (!IsEnd(j)) {
// Apply route fixed cost on first non-first/last node, in other words on
// the arc from the first node to its next node if it's not the last node.
cost = costs_[vehicle]->Run(node_i, node_j)
+ fixed_costs_[index_to_vehicle_[i]];
} else {
cost = 0;
}
cache.node = j;
cache.vehicle = vehicle;
cache.cost = cost;
return cost;
}
int64 RoutingModel::GetPenaltyCost(int64 i) const {
int index = kUnassigned;
if (FindCopy(node_to_disjunction_, i, &index)) {
const Disjunction& disjunction = disjunctions_[index];
int64 penalty = disjunction.penalty;
if (penalty > 0 && disjunction.nodes.size() == 1) {
return penalty;
} else {
return 0;
}
} else {
return 0;
}
}
bool RoutingModel::IsStart(int64 index) const {
return !IsEnd(index) && index_to_vehicle_[index] != kUnassigned;
}
int64 RoutingModel::GetCost(int64 i, int64 j, int64 vehicle) {
if (i != j) {
return GetArcCost(i, j, vehicle);
} else {
return 0;
}
}
int64 RoutingModel::GetFilterCost(int64 i, int64 j, int64 vehicle) {
if (i != j) {
return GetArcCost(i, j, vehicle);
} else {
return GetPenaltyCost(i);
}
}
// Return high cost if connecting to end node; used in cost-based first solution
int64 RoutingModel::GetFirstSolutionCost(int64 i, int64 j) {
if (j < nodes_) {
// TODO(user): Take vehicle into account.
return GetCost(i, j, 0);
} else {
return kint64max;
}
}
void RoutingModel::CheckDepot() {
if (!is_depot_set_) {
LOG(WARNING) << "A depot must be specified, setting one at node 0";
SetDepot(0);
}
}
#define CP_ROUTING_PUSH_BACK_OPERATOR(operator_type) \
if (homogeneous_costs_) { \
operators.push_back(solver_->MakeOperator(nexts_.get(), \
size, \
operator_type)); \
} else { \
operators.push_back(solver_->MakeOperator(nexts_.get(), \
vehicle_vars_.get(), \
size, \
operator_type)); \
}
#define CP_ROUTING_PUSH_BACK_CALLBACK_OPERATOR(operator_type) \
if (homogeneous_costs_) { \
operators.push_back(solver_->MakeOperator(nexts_.get(), \
size, \
BuildCostCallback(), \
operator_type)); \
} else { \
operators.push_back(solver_->MakeOperator(nexts_.get(), \
vehicle_vars_.get(), \
size, \
BuildCostCallback(), \
operator_type)); \
}
void RoutingModel::SetUpSearch() {
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<VarMap> 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<LocalSearchOperator*> operators = extra_operators_;
if (vehicles_ > 1) {
if (!FLAGS_routing_no_relocate) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::RELOCATE);
}
if (!FLAGS_routing_no_exchange) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::EXCHANGE);
}
if (!FLAGS_routing_no_cross) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::CROSS);
}
}
if (!FLAGS_routing_no_lkh
&& !FLAGS_routing_tabu_search
&& !FLAGS_routing_simulated_annealing) {
CP_ROUTING_PUSH_BACK_CALLBACK_OPERATOR(Solver::LK);
}
if (!FLAGS_routing_no_2opt) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::TWOOPT);
}
if (!FLAGS_routing_no_oropt) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::OROPT);
}
if (!FLAGS_routing_no_make_active && disjunctions_.size() != 0) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::MAKEINACTIVE);
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::MAKEACTIVE);
if (!FLAGS_routing_use_extended_swap_active) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::SWAPACTIVE);
} else {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::EXTENDEDSWAPACTIVE);
}
}
// TODO(user): move the following operators to a second local search loop.
if (!FLAGS_routing_no_tsp
&& !FLAGS_routing_tabu_search
&& !FLAGS_routing_simulated_annealing) {
CP_ROUTING_PUSH_BACK_CALLBACK_OPERATOR(Solver::TSPOPT);
}
if (!FLAGS_routing_no_tsplns
&& !FLAGS_routing_tabu_search
&& !FLAGS_routing_simulated_annealing) {
CP_ROUTING_PUSH_BACK_CALLBACK_OPERATOR(Solver::TSPLNS);
}
if (!FLAGS_routing_no_lns) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::PATHLNS);
if (disjunctions_.size() != 0) {
CP_ROUTING_PUSH_BACK_OPERATOR(Solver::UNACTIVELNS);
}
}
LocalSearchOperator* local_search_operator =
solver_->ConcatenateOperators(operators);
DecisionBuilder* finalize_solution =
solver_->MakePhase(nexts_.get(), size,
Solver::CHOOSE_FIRST_UNBOUND,
Solver::ASSIGN_MIN_VALUE);
DecisionBuilder* first_solution = finalize_solution;
switch (GetSelectedFirstSolutionStrategy()) {
case ROUTING_GLOBAL_CHEAPEST_ARC:
LG << "Using ROUTING_GLOBAL_CHEAPEST_ARC";
first_solution =
solver_->MakePhase(nexts_.get(), size,
NewPermanentCallback(
this,
&RoutingModel::GetFirstSolutionCost),
Solver::CHOOSE_STATIC_GLOBAL_BEST);
break;
case ROUTING_LOCAL_CHEAPEST_ARC:
LG << "Using ROUTING_LOCAL_CHEAPEST_ARC";
first_solution =
solver_->MakePhase(nexts_.get(), size,
Solver::CHOOSE_FIRST_UNBOUND,
NewPermanentCallback(
this,
&RoutingModel::GetFirstSolutionCost));
break;
case ROUTING_PATH_CHEAPEST_ARC:
LG << "Using ROUTING_PATH_CHEAPEST_ARC";
first_solution =
solver_->MakePhase(nexts_.get(), size,
Solver::CHOOSE_PATH,
NewPermanentCallback(
this,
&RoutingModel::GetFirstSolutionCost));
break;
case ROUTING_EVALUATOR_STRATEGY:
LG << "Using ROUTING_EVALUATOR_STRATEGY";
CHECK(first_solution_evaluator_ != NULL);
first_solution =
solver_->MakePhase(nexts_.get(), size,
Solver::CHOOSE_PATH,
NewPermanentCallback(
first_solution_evaluator_.get(),
&Solver::IndexEvaluator2::Run));
break;
case ROUTING_DEFAULT_STRATEGY:
LG << "Using DEFAULT";
break;
default:
LOG(WARNING) << "Unknown argument for routing_first_solution, "
"using default";
}
if (FLAGS_routing_use_first_solution_dive) {
DecisionBuilder* apply =
solver_->MakeApplyBranchSelector(NewPermanentCallback(&LeftDive));
first_solution = solver_->Compose(apply, first_solution);
}
std::vector<LocalSearchFilter*> 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_path_cumul_filter) {
for (ConstIter<VarMap> 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)));
}
}
LocalSearchPhaseParameters* parameters =
solver_->MakeLocalSearchPhaseParameters(
local_search_operator,
solver_->MakeSolveOnce(finalize_solution, lns_limit_),
ls_limit_,
filters);
if (FLAGS_routing_dfs) {
solve_db_ = finalize_solution;
} else {
if (homogeneous_costs_) {
solve_db_ = solver_->MakeLocalSearchPhase(nexts_.get(),
size,
first_solution,
parameters);
} else {
const int all_size = size + size + vehicles_;
scoped_array<IntVar*> 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);
}
}
DecisionBuilder* restore_preassignment =
solver_->MakeRestoreAssignment(preassignment_);
solve_db_ = solver_->Compose(restore_preassignment, solve_db_);
improve_db_ =
solver_->Compose(restore_preassignment,
solver_->MakeLocalSearchPhase(assignment_, parameters));
restore_assignment_ =
solver_->Compose(solver_->MakeRestoreAssignment(assignment_),
finalize_solution);
if (FLAGS_routing_trace) {
const int kLogPeriod = 10000;
SearchMonitor* trace = solver_->MakeSearchLog(kLogPeriod, cost_);
monitors_.push_back(trace);
}
}
#undef CP_ROUTING_PUSH_BACK_CALLBACK_OPERATOR
#undef CP_ROUTING_PUSH_BACK_OPERATOR
IntVar* RoutingModel::CumulVar(int64 node, const string& name) const {
IntVar** named_cumuls = FindPtrOrNull(cumuls_, name);
if (named_cumuls != NULL) {
return named_cumuls[node];
} else {
return NULL;
}
}
IntVar* RoutingModel::TransitVar(int64 node, const string& name) const {
IntVar** named_transits = FindPtrOrNull(transits_, name);
if (named_transits != NULL) {
return named_transits[node];
} else {
return NULL;
}
}
void RoutingModel::AddToAssignment(IntVar* var) {
extra_vars_.push_back(var);
}
Solver::IndexEvaluator2* RoutingModel::NewCachedCallback(
Solver::IndexEvaluator2* callback) {
const int size = Size() + vehicles_;
if (FLAGS_routing_cache_callbacks && size <= FLAGS_routing_max_cache_size) {
routing_caches_.push_back(new RoutingCache(callback, size));
Solver::IndexEvaluator2* cached_evaluator =
NewPermanentCallback(routing_caches_.back(), &RoutingCache::Run);
owned_callbacks_.insert(cached_evaluator);
return cached_evaluator;
} else {
owned_callbacks_.insert(callback);
return callback;
}
}
Solver::IndexEvaluator3* RoutingModel::BuildCostCallback() {
return NewPermanentCallback(this, &RoutingModel::GetCost);
}
IntVar** RoutingModel::GetOrMakeCumuls(int64 capacity, const string& name) {
IntVar** named_cumuls = FindPtrOrNull(cumuls_, name);
if (named_cumuls == NULL) {
std::vector<IntVar*> cumuls;
const int size = Size() + vehicles_;
solver_->MakeIntVarArray(size, 0LL, capacity, name, &cumuls);
IntVar** cumul_array = solver_->RevAlloc(new IntVar*[size]);
memcpy(cumul_array, cumuls.data(), cumuls.size() * sizeof(*cumuls.data()));
cumuls_[name] = cumul_array;
return cumul_array;
}
return named_cumuls;
}
int64 RoutingModel::WrappedEvaluator(Solver::IndexEvaluator2* evaluator,
int64 from,
int64 to) {
return evaluator->Run(IndexToNode(from), IndexToNode(to));
}
IntVar** RoutingModel::GetOrMakeTransits(Solver::IndexEvaluator2* evaluator,
int64 slack_max,
int64 capacity,
const string& name) {
evaluator->CheckIsRepeatable();
IntVar** named_transits = FindPtrOrNull(transits_, name);
if (named_transits == NULL) {
std::vector<IntVar*> transits;
const int size = Size();
IntVar** transit_array = solver_->RevAlloc(new IntVar*[size]);
for (int i = 0; i < size; ++i) {
IntVar* fixed_transit =
solver_->MakeElement(NewPermanentCallback(
this,
&RoutingModel::WrappedEvaluator,
evaluator,
static_cast<int64>(i)),
nexts_[i])->Var();
if (slack_max == 0) {
transit_array[i] = fixed_transit;
} else {
IntVar* slack_var = solver_->MakeIntVar(0, slack_max, "slack");
transit_array[i] = solver_->MakeSum(slack_var, fixed_transit)->Var();
}
transit_array[i]->SetMin(0);
transit_array[i]->SetMax(capacity);
}
transits_[name] = transit_array;
transit_evaluators_[name] =
NewPermanentCallback(this,
&RoutingModel::WrappedEvaluator,
evaluator);
owned_callbacks_.insert(transit_evaluators_[name]);
owned_callbacks_.insert(evaluator);
return transit_array;
}
return named_transits;
}
} // namespace operations_research