small improvements to the routing library; fix disjunctive constraints with variable durations intervals

This commit is contained in:
Laurent Perron
2016-09-12 13:40:24 +02:00
parent d135e41e84
commit 04d74180ae
6 changed files with 147 additions and 155 deletions

View File

@@ -3262,7 +3262,9 @@ IntExpr* Solver::MakeSum(const std::vector<IntVar*>& vars) {
IntExpr* Solver::MakeMin(const std::vector<IntVar*>& vars) {
const int size = vars.size();
if (size == 0) {
return MakeIntConst(0LL);
LOG(WARNING) << "operations_research::Solver::MakeMin() was called with an "
"empty list of variables. Was this intentional?";
return MakeIntConst(kint64max);
} else if (size == 1) {
return vars[0];
} else if (size == 2) {
@@ -3303,7 +3305,9 @@ IntExpr* Solver::MakeMin(const std::vector<IntVar*>& vars) {
IntExpr* Solver::MakeMax(const std::vector<IntVar*>& vars) {
const int size = vars.size();
if (size == 0) {
return MakeIntConst(0LL);
LOG(WARNING) << "operations_research::Solver::MakeMax() was called with an "
"empty list of variables. Was this intentional?";
return MakeIntConst(kint64min);
} else if (size == 1) {
return vars[0];
} else if (size == 2) {
@@ -3357,7 +3361,9 @@ Constraint* Solver::MakeMinEquality(const std::vector<IntVar*>& vars,
} else if (size == 1) {
return MakeEquality(vars[0], min_var);
} else {
return MakeEquality(min_var, Zero());
LOG(WARNING) << "operations_research::Solver::MakeMinEquality() was called "
"with an empty list of variables. Was this intentional?";
return MakeEquality(min_var, kint64max);
}
}
@@ -3377,7 +3383,9 @@ Constraint* Solver::MakeMaxEquality(const std::vector<IntVar*>& vars,
} else if (size == 1) {
return MakeEquality(vars[0], max_var);
} else {
return MakeEquality(max_var, Zero());
LOG(WARNING) << "operations_research::Solver::MakeMaxEquality() was called "
"with an empty list of variables. Was this intentional?";
return MakeEquality(max_var, kint64min);
}
}

View File

@@ -962,22 +962,8 @@ Constraint* Solver::MakeNotBetweenCt(IntExpr* e, int64 l, int64 u) {
// Catch one-sided constraints.
if (emin >= l) return MakeGreater(e, u);
if (emax <= u) return MakeLess(e, l);
// // Simplify the common factor, if any.
// TODO(user): Add back simplification.
// int64 coeff = ExtractExprProductCoeff(&e);
// if (coeff != 1) {
// CHECK_NE(coeff, 0); // Would have been caught by the trivial cases already.
// if (coeff < 0) {
// std::swap(u, l);
// u = -u;
// l = -l;
// coeff = -coeff;
// }
// return MakeBetweenCt(e, PosIntDivUp(l, coeff), PosIntDivDown(u, coeff));
// } else {
// No further reduction is possible.
return RevAlloc(new NotBetweenCt(this, e, l, u));
// }
// TODO(user): Add back simplification code if e is constant * other_expr.
return RevAlloc(new NotBetweenCt(this, e, l, u));
}
// ----- is_between_cst Constraint -----
@@ -1171,8 +1157,8 @@ Constraint* Solver::MakeMemberCt(IntExpr* expr, const std::vector<int64>& values
const int64 coeff = ExtractExprProductCoeff(&expr);
if (coeff == 0) {
return std::find(values.begin(), values.end(), 0) == values.end()
? MakeFalseConstraint()
: MakeTrueConstraint();
? MakeFalseConstraint()
: MakeTrueConstraint();
}
std::vector<int64> copied_values = values;
// If the expression is a non-trivial product, we filter out the values that
@@ -1240,8 +1226,8 @@ Constraint* Solver::MakeNotMemberCt(IntExpr* expr,
const int64 coeff = ExtractExprProductCoeff(&expr);
if (coeff == 0) {
return std::find(values.begin(), values.end(), 0) == values.end()
? MakeTrueConstraint()
: MakeFalseConstraint();
? MakeTrueConstraint()
: MakeFalseConstraint();
}
std::vector<int64> copied_values = values;
// If the expression is a non-trivial product, we filter out the values that

View File

@@ -113,55 +113,6 @@ std::string RangeLessOrEqual::DebugString() const {
return left_->DebugString() + " <= " + right_->DebugString();
}
//-----------------------------------------------------------------------------
// RangeGreaterOrEqual
class RangeGreaterOrEqual : public Constraint {
public:
RangeGreaterOrEqual(Solver* const s, IntExpr* const l, IntExpr* const r);
~RangeGreaterOrEqual() override {}
void Post() override;
void InitialPropagate() override;
std::string DebugString() const override;
IntVar* Var() override {
return solver()->MakeIsGreaterOrEqualVar(left_, right_);
}
void Accept(ModelVisitor* const visitor) const override {
visitor->BeginVisitConstraint(ModelVisitor::kGreaterOrEqual, this);
visitor->VisitIntegerExpressionArgument(ModelVisitor::kLeftArgument, left_);
visitor->VisitIntegerExpressionArgument(ModelVisitor::kRightArgument,
right_);
visitor->EndVisitConstraint(ModelVisitor::kGreaterOrEqual, this);
}
private:
IntExpr* const left_;
IntExpr* const right_;
Demon* demon_;
};
RangeGreaterOrEqual::RangeGreaterOrEqual(Solver* const s, IntExpr* const l,
IntExpr* const r)
: Constraint(s), left_(l), right_(r), demon_(nullptr) {}
void RangeGreaterOrEqual::Post() {
demon_ = solver()->MakeConstraintInitialPropagateCallback(this);
left_->WhenRange(demon_);
right_->WhenRange(demon_);
}
void RangeGreaterOrEqual::InitialPropagate() {
left_->SetMin(right_->Min());
right_->SetMax(left_->Max());
if (left_->Min() >= right_->Max()) {
demon_->inhibit(solver());
}
}
std::string RangeGreaterOrEqual::DebugString() const {
return left_->DebugString() + " >= " + right_->DebugString();
}
//-----------------------------------------------------------------------------
// RangeLess
@@ -208,52 +159,6 @@ std::string RangeLess::DebugString() const {
return left_->DebugString() + " < " + right_->DebugString();
}
//-----------------------------------------------------------------------------
// RangeGreater
class RangeGreater : public Constraint {
public:
RangeGreater(Solver* const s, IntExpr* const l, IntExpr* const r);
~RangeGreater() override {}
void Post() override;
void InitialPropagate() override;
std::string DebugString() const override;
IntVar* Var() override { return solver()->MakeIsGreaterVar(left_, right_); }
void Accept(ModelVisitor* const visitor) const override {
visitor->BeginVisitConstraint(ModelVisitor::kGreater, this);
visitor->VisitIntegerExpressionArgument(ModelVisitor::kLeftArgument, left_);
visitor->VisitIntegerExpressionArgument(ModelVisitor::kRightArgument,
right_);
visitor->EndVisitConstraint(ModelVisitor::kGreater, this);
}
private:
IntExpr* const left_;
IntExpr* const right_;
Demon* demon_;
};
RangeGreater::RangeGreater(Solver* const s, IntExpr* const l, IntExpr* const r)
: Constraint(s), left_(l), right_(r), demon_(nullptr) {}
void RangeGreater::Post() {
demon_ = solver()->MakeConstraintInitialPropagateCallback(this);
left_->WhenRange(demon_);
right_->WhenRange(demon_);
}
void RangeGreater::InitialPropagate() {
left_->SetMin(right_->Min() + 1);
right_->SetMax(left_->Max() - 1);
if (left_->Min() > right_->Max()) {
demon_->inhibit(solver());
}
}
std::string RangeGreater::DebugString() const {
return left_->DebugString() + " > " + right_->DebugString();
}
//-----------------------------------------------------------------------------
// DiffVar
@@ -637,19 +542,7 @@ Constraint* Solver::MakeLessOrEqual(IntExpr* const l, IntExpr* const r) {
}
Constraint* Solver::MakeGreaterOrEqual(IntExpr* const l, IntExpr* const r) {
CHECK(l != nullptr) << "left expression nullptr, maybe a bad cast";
CHECK(r != nullptr) << "left expression nullptr, maybe a bad cast";
CHECK_EQ(this, l->solver());
CHECK_EQ(this, r->solver());
if (l == r) {
return MakeTrueConstraint();
} else if (l->Bound()) {
return MakeLessOrEqual(r, l->Min());
} else if (r->Bound()) {
return MakeGreaterOrEqual(l, r->Min());
} else {
return RevAlloc(new RangeGreaterOrEqual(this, l, r));
}
return MakeLessOrEqual(r, l);
}
Constraint* Solver::MakeLess(IntExpr* const l, IntExpr* const r) {
@@ -667,17 +560,7 @@ Constraint* Solver::MakeLess(IntExpr* const l, IntExpr* const r) {
}
Constraint* Solver::MakeGreater(IntExpr* const l, IntExpr* const r) {
CHECK(l != nullptr) << "left expression nullptr, maybe a bad cast";
CHECK(r != nullptr) << "left expression nullptr, maybe a bad cast";
CHECK_EQ(this, l->solver());
CHECK_EQ(this, r->solver());
if (l->Bound()) {
return MakeLess(r, l->Min());
} else if (r->Bound()) {
return MakeGreater(l, r->Min());
} else {
return RevAlloc(new RangeGreater(this, l, r));
}
return MakeLess(r, l);
}
Constraint* Solver::MakeNonEquality(IntExpr* const l, IntExpr* const r) {

View File

@@ -158,7 +158,21 @@ struct ThetaNode {
// Single interval element
explicit ThetaNode(const IntervalVar* const interval)
: total_processing(interval->DurationMin()),
total_ect(interval->EndMin()) {}
total_ect(interval->StartMin() + interval->DurationMin()) {
// NOTE(user): Petr Vilim's thesis assumes that all tasks in the
// scheduling problem have fixed duration and that propagation already
// updated the bounds of the start/end times accordingly.
// The problem in this case is that the recursive formula for computing
// total_ect was only proved for the case where the duration is fixed; in
// our case, we use StartMin() + DurationMin() for the earliest completion
// time of a task, which should not break any assumptions, but may give
// bounds that are too loose.
// LOG_IF_FIRST_N(WARNING,
// (interval->DurationMin() != interval->DurationMax()), 1)
// << "You are using the Theta-tree on tasks having variable durations. "
// "This may lead to unexpected results, such as discarding valid "
// "solutions or allowing invalid ones.";
}
void Compute(const ThetaNode& left, const ThetaNode& right) {
total_processing = left.total_processing + right.total_processing;
@@ -171,9 +185,8 @@ struct ThetaNode {
}
std::string DebugString() const {
return StringPrintf("ThetaNode{ p = %" GG_LL_FORMAT "d, e = %" GG_LL_FORMAT
"d }",
total_processing, total_ect < 0LL ? -1LL : total_ect);
return StrCat("ThetaNode{ p = ", total_processing, ", e = ",
total_ect < 0LL ? -1LL : total_ect, " }");
}
int64 total_processing;

View File

@@ -33,6 +33,7 @@
#include "base/thorough_hash.h"
#include "base/hash.h"
#include "constraint_solver/model.pb.h"
#include "graph/connectivity.h"
#include "graph/linear_assignment.h"
#include "util/saturated_arithmetic.h"
@@ -2198,7 +2199,8 @@ void RoutingModel::CloseModel() {
class RoutingModelInspector : public ModelVisitor {
public:
explicit RoutingModelInspector(RoutingModel* model) {
explicit RoutingModelInspector(RoutingModel* model) : model_(model) {
same_vehicle_components_.Init(model->Size());
for (const std::string& name : model->GetAllDimensionNames()) {
RoutingDimension* const dimension = model->GetMutableDimension(name);
const std::vector<IntVar*>& cumuls = dimension->cumuls();
@@ -2206,9 +2208,35 @@ class RoutingModelInspector : public ModelVisitor {
cumul_to_dim_indices_[cumuls[i]] = {dimension, i};
}
}
const std::vector<IntVar*>& vehicle_vars = model->VehicleVars();
for (int i = 0; i < vehicle_vars.size(); ++i) {
vehicle_var_to_indices_[vehicle_vars[i]] = i;
}
RegisterInspectors();
}
~RoutingModelInspector() override {}
void EndVisitModel(const std::string& solver_name) override {
// Compact same vehicle component indices.
hash_map<int, int> component_indices;
int component_index = 0;
for (int node = 0; node < model_->Size(); ++node) {
const int component =
same_vehicle_components_.GetClassRepresentative(node);
if (InsertIfNotPresent(&component_indices, component, component_index)) {
++component_index;
}
}
model_->InitSameVehicleGroups(component_indices.size());
for (int node = 0; node < model_->Size(); ++node) {
const int component =
same_vehicle_components_.GetClassRepresentative(node);
DCHECK(ContainsKey(component_indices, component));
model_->SetSameVehicleGroup(
node, FindWithDefault(component_indices, component, 0));
}
// TODO(user): Perform transitive closure of dimension precedence graphs.
// TODO(user): Have a single annotated precedence graph.
}
void EndVisitConstraint(const std::string& type_name,
const Constraint* const constraint) override {
FindWithDefault(constraint_inspectors_, type_name, []() {})();
@@ -2233,31 +2261,73 @@ class RoutingModelInspector : public ModelVisitor {
expr_inspectors_[kExpressionArgument] = [this](const IntExpr* expr) {
expr_ = expr;
};
expr_inspectors_[kLeftArgument] = [this](const IntExpr* expr) {
left_ = expr;
};
expr_inspectors_[kRightArgument] = [this](const IntExpr* expr) {
right_ = expr;
};
array_inspectors_[kStartsArgument] = [this](
const std::vector<int64>& int_array) { starts_argument_ = int_array; };
array_inspectors_[kEndsArgument] = [this](const std::vector<int64>& int_array) {
ends_argument_ = int_array;
};
constraint_inspectors_[kNotMember] = [this]() {
const auto dim_index = cumul_to_dim_indices_[expr_];
RoutingDimension* const dimension = dim_index.first;
const int index = dim_index.second;
dimension->forbidden_intervals_[index].InsertIntervals(starts_argument_,
ends_argument_);
VLOG(2) << dimension->name() << " " << index << ": "
<< dimension->forbidden_intervals_[index].DebugString();
std::pair<RoutingDimension*, int> dim_index;
if (FindCopy(cumul_to_dim_indices_, expr_, &dim_index)) {
RoutingDimension* const dimension = dim_index.first;
const int index = dim_index.second;
dimension->forbidden_intervals_[index].InsertIntervals(starts_argument_,
ends_argument_);
VLOG(2) << dimension->name() << " " << index << ": "
<< dimension->forbidden_intervals_[index].DebugString();
}
expr_ = nullptr;
starts_argument_.clear();
ends_argument_.clear();
};
constraint_inspectors_[kEquality] = [this]() {
int left_index = 0;
int right_index = 0;
if (FindCopy(vehicle_var_to_indices_, left_, &left_index) &&
FindCopy(vehicle_var_to_indices_, right_, &right_index)) {
VLOG(2) << "Vehicle variables for " << left_index << " and "
<< right_index << " are equal.";
same_vehicle_components_.AddArc(left_index, right_index);
}
left_ = nullptr;
right_ = nullptr;
};
constraint_inspectors_[kLessOrEqual] = [this]() {
std::pair<RoutingDimension*, int> left_index;
std::pair<RoutingDimension*, int> right_index;
if (FindCopy(cumul_to_dim_indices_, left_, &left_index) &&
FindCopy(cumul_to_dim_indices_, right_, &right_index)) {
RoutingDimension* const dimension = left_index.first;
if (dimension == right_index.first) {
VLOG(2) << "For dimension " << dimension->name() << ", cumul for "
<< left_index.second << " is less than " << right_index.second
<< ".";
dimension->precedence_graph_.AddArc(left_index.second,
right_index.second);
}
}
left_ = nullptr;
right_ = nullptr;
};
}
RoutingModel* const model_;
ConnectedComponents<int, int> same_vehicle_components_;
hash_map<const IntExpr*, std::pair<RoutingDimension*, int>>
cumul_to_dim_indices_;
hash_map<const IntExpr*, int> vehicle_var_to_indices_;
hash_map<std::string, ExprInspector> expr_inspectors_;
hash_map<std::string, ArrayInspector> array_inspectors_;
hash_map<std::string, ConstraintInspector> constraint_inspectors_;
const IntExpr* expr_ = nullptr;
const IntExpr* left_ = nullptr;
const IntExpr* right_ = nullptr;
std::vector<int64> starts_argument_;
std::vector<int64> ends_argument_;
};

View File

@@ -173,6 +173,7 @@
#include "constraint_solver/constraint_solver.h"
#include "constraint_solver/constraint_solveri.h"
#include "constraint_solver/routing_parameters.pb.h"
#include "graph/graph.h"
#include "util/range_query_function.h"
#include "util/sorted_interval_list.h"
#include "base/adjustable_priority_queue-inl.h"
@@ -586,6 +587,9 @@ class RoutingModel {
//
// TODO(user): Remove this when model introspection detects linked nodes.
void AddPickupAndDelivery(NodeIndex node1, NodeIndex node2) {
// TODO(user): Checking the depot ensures indices are up-to-date but sets
// the depot to node 0 if it has not been set already; find a way to avoid
// this.
CheckDepot();
pickup_delivery_pairs_.push_back(
std::make_pair(NodeToIndex(node1), NodeToIndex(node2)));
@@ -874,6 +878,11 @@ class RoutingModel {
}
// Returns the number of different vehicle classes in the model.
int GetVehicleClassesCount() const { return vehicle_classes_.size(); }
// Returns variable indices of nodes constrained to be on the same route.
const std::vector<int>& GetSameVehicleIndicesOfIndex(int node) const {
DCHECK(closed_);
return same_vehicle_groups_[same_vehicle_group_[node]];
}
// Returns whether the arc from->to1 is more constrained than from->to2,
// taking into account, in order:
// - whether the destination node isn't an end node
@@ -1191,6 +1200,15 @@ class RoutingModel {
int64 GetArcCostForCostClassInternal(int64 i, int64 j, int64 cost_class);
int GetVehicleStartClass(int64 start) const;
void InitSameVehicleGroups(int number_of_groups) {
same_vehicle_group_.assign(Size(), 0);
same_vehicle_groups_.assign(number_of_groups, {});
}
void SetSameVehicleGroup(int index, int group) {
same_vehicle_group_[index] = group;
same_vehicle_groups_[group].push_back(index);
}
// Model
std::unique_ptr<Solver> solver_;
int nodes_;
@@ -1236,6 +1254,10 @@ class RoutingModel {
std::vector<ValuedNodes<int64> > same_vehicle_costs_;
// Pickup and delivery
NodePairs pickup_delivery_pairs_;
// Same vehicle group to which a node belongs.
std::vector<int> same_vehicle_group_;
// Same vehicle node groups.
std::vector<std::vector<int> > same_vehicle_groups_;
// Index management
std::vector<NodeIndex> index_to_node_;
ITIVector<NodeIndex, int> node_to_index_;
@@ -1282,6 +1304,7 @@ class RoutingModel {
hash_set<const VariableNodeEvaluator2*> owned_state_dependent_callbacks_;
friend class RoutingDimension;
friend class RoutingModelInspector;
DISALLOW_COPY_AND_ASSIGN(RoutingModel);
};
@@ -1509,6 +1532,12 @@ class RoutingDimension {
const std::string& name() const { return name_; }
// Accessors.
#ifndef SWIG
const ReverseArcListGraph<int, int>& GetPrecedenceGraph() const {
return precedence_graph_;
}
#endif
int64 GetSpanUpperBoundForVehicle(int vehicle) const {
return vehicle_span_upper_bounds_[vehicle];
}
@@ -1574,6 +1603,9 @@ class RoutingDimension {
// "class_evaluators_" does the de-duplicated ownership.
std::vector<RoutingModel::TransitEvaluator2> class_evaluators_;
std::vector<int64> vehicle_to_class_;
#ifndef SWIG
ReverseArcListGraph<int, int> precedence_graph_;
#endif
// The transits of a dimension may depend on its cumuls or the cumuls of
// another dimension. There can be no cycles, except for self loops, a typical