continue working on routing breaks
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -488,14 +488,18 @@ class RoutingModel {
|
||||
const std::function<int64(int64)>& f, int64 domain_start,
|
||||
int64 domain_end);
|
||||
|
||||
// For every vehicle of the routing model, if total_slacks[vehicle] is not
|
||||
// nullptr, constrains it to be the sum of slacks on that vehicle, that is:
|
||||
// dimension->CumulVar(end) - dimension->CumulVar(start) -
|
||||
// sum_{node in path of vehicle} dimension->FixedTransitVar(node).
|
||||
// For every vehicle of the routing model:
|
||||
// - if total_slacks[vehicle] is not nullptr, constrains it to be the sum of
|
||||
// slacks on that vehicle, that is,
|
||||
// dimension->CumulVar(end) - dimension->CumulVar(start) -
|
||||
// sum_{node in path of vehicle} dimension->FixedTransitVar(node).
|
||||
// - if spans[vehicle] is not nullptr, constrains it to be
|
||||
// dimension->CumulVar(end) - dimension->CumulVar(start)
|
||||
// This does stronger propagation than a decomposition, and takes breaks into
|
||||
// account.
|
||||
Constraint* MakePathSlacks(const RoutingDimension* dimension,
|
||||
std::vector<IntVar*> total_slacks);
|
||||
Constraint* MakePathSpansAndTotalSlacks(const RoutingDimension* dimension,
|
||||
std::vector<IntVar*> spans,
|
||||
std::vector<IntVar*> total_slacks);
|
||||
|
||||
// Outputs the names of all dimensions added to the routing engine.
|
||||
// TODO(user): rename.
|
||||
@@ -696,6 +700,8 @@ class RoutingModel {
|
||||
int GetVisitType(int64 index) const;
|
||||
// This function should be called once all node visit types have been set and
|
||||
// prior to adding any incompatibilities/requirements.
|
||||
// TODO(user): Reconsider the logic and potentially remove the need to
|
||||
// "close" types.
|
||||
void CloseVisitTypes();
|
||||
int GetNumberOfVisitTypes() const { return num_visit_types_; }
|
||||
// Incompatibilities:
|
||||
@@ -718,22 +724,35 @@ class RoutingModel {
|
||||
return has_temporal_type_incompatibilities_;
|
||||
}
|
||||
// Requirements:
|
||||
// If type_D temporally depends on type_R, any non-delivery node_D of type_D
|
||||
// requires at least one non-delivered node of type_R on its vehicle at the
|
||||
// time node_D is visited.
|
||||
// NOTE: As of 2019-04, cycles in the requirement graph are not supported,
|
||||
// and lead to the dependent nodes being skipped if possible (otherwise
|
||||
// the model is considered infeasible).
|
||||
// The following function specifies that "dependent_type" requires at least
|
||||
// The following functions specify that "dependent_type" requires at least
|
||||
// one of the types in "required_type_alternatives".
|
||||
//
|
||||
// For same-vehicle requirements, a node of dependent type type_D requires at
|
||||
// least one node of type type_R among the required alternatives on the same
|
||||
// route.
|
||||
void AddSameVehicleRequiredTypeAlternatives(
|
||||
int dependent_type, absl::flat_hash_set<int> required_type_alternatives);
|
||||
// If type_D temporally depends on type_R, any non-delivery node_D of type_D
|
||||
// requires at least one non-delivered node of type_R on its vehicle at the
|
||||
// time node_D is visited.
|
||||
void AddTemporalRequiredTypeAlternatives(
|
||||
int dependent_type, absl::flat_hash_set<int> required_type_alternatives);
|
||||
// clang-format off
|
||||
// Returns all sets of requirement alternatives for the given type.
|
||||
// Returns the sets of same-vehicle/temporal requirement alternatives for the
|
||||
// given type.
|
||||
const std::vector<absl::flat_hash_set<int> >&
|
||||
GetSameVehicleRequiredTypeAlternativesOfType(int type) const;
|
||||
const std::vector<absl::flat_hash_set<int> >&
|
||||
GetTemporalRequiredTypeAlternativesOfType(int type) const;
|
||||
// clang-format on
|
||||
// Returns true iff any type requirements have been added to the model.
|
||||
// Returns true iff any same-route (resp. temporal) type requirements have
|
||||
// been added to the model.
|
||||
bool HasSameVehicleTypeRequirements() const {
|
||||
return has_same_vehicle_type_requirements_;
|
||||
}
|
||||
bool HasTemporalTypeRequirements() const {
|
||||
return has_temporal_type_requirements_;
|
||||
}
|
||||
@@ -742,7 +761,8 @@ class RoutingModel {
|
||||
// on node types.
|
||||
bool HasTypeRegulations() const {
|
||||
return HasTemporalTypeIncompatibilities() ||
|
||||
HasHardTypeIncompatibilities() || HasTemporalTypeRequirements();
|
||||
HasHardTypeIncompatibilities() || HasSameVehicleTypeRequirements() ||
|
||||
HasTemporalTypeRequirements();
|
||||
}
|
||||
|
||||
// Get the "unperformed" penalty of a node. This is only well defined if the
|
||||
@@ -982,8 +1002,8 @@ class RoutingModel {
|
||||
// Adds an extra variable to the vehicle routing assignment.
|
||||
void AddToAssignment(IntVar* const var);
|
||||
void AddIntervalToAssignment(IntervalVar* const interval);
|
||||
// For every dimension in the model's dimensions_for_global_optimizer_, this
|
||||
// method tries to pack the cumul values of the dimension, such that:
|
||||
// For every dimension in the model's dimensions_for_local/global_optimizer_,
|
||||
// this method tries to pack the cumul values of the dimension, such that:
|
||||
// - The cumul costs (span costs, soft lower and upper bound costs, etc) are
|
||||
// minimized.
|
||||
// - The cumuls of the ends of the routes are minimized for this given minimal
|
||||
@@ -991,7 +1011,7 @@ class RoutingModel {
|
||||
// - Given these minimal end cumuls, the route start cumuls are maximized.
|
||||
// Returns the assignment resulting from allocating these packed cumuls with
|
||||
// the solver, and nullptr if these cumuls could not be set by the solver.
|
||||
const Assignment* PackCumulsOfGlobalOptimizerDimensionsFromAssignment(
|
||||
const Assignment* PackCumulsOfOptimizerDimensionsFromAssignment(
|
||||
const Assignment* original_assignment, absl::Duration duration_limit);
|
||||
#ifndef SWIG
|
||||
// TODO(user): Revisit if coordinates are added to the RoutingModel class.
|
||||
@@ -1058,19 +1078,20 @@ class RoutingModel {
|
||||
IntVar* CostVar() const { return cost_; }
|
||||
// Returns the cost of the transit arc between two nodes for a given vehicle.
|
||||
// Input are variable indices of node. This returns 0 if vehicle < 0.
|
||||
int64 GetArcCostForVehicle(int64 from_index, int64 to_index, int64 vehicle);
|
||||
int64 GetArcCostForVehicle(int64 from_index, int64 to_index,
|
||||
int64 vehicle) const;
|
||||
// Whether costs are homogeneous across all vehicles.
|
||||
bool CostsAreHomogeneousAcrossVehicles() const {
|
||||
return costs_are_homogeneous_across_vehicles_;
|
||||
}
|
||||
// Returns the cost of the segment between two nodes supposing all vehicle
|
||||
// costs are the same (returns the cost for the first vehicle otherwise).
|
||||
int64 GetHomogeneousCost(int64 from_index, int64 to_index) {
|
||||
int64 GetHomogeneousCost(int64 from_index, int64 to_index) const {
|
||||
return GetArcCostForVehicle(from_index, to_index, /*vehicle=*/0);
|
||||
}
|
||||
// Returns the cost of the arc in the context of the first solution strategy.
|
||||
// This is typically a simplification of the actual cost; see the .cc.
|
||||
int64 GetArcCostForFirstSolution(int64 from_index, int64 to_index);
|
||||
int64 GetArcCostForFirstSolution(int64 from_index, int64 to_index) const;
|
||||
// Returns the cost of the segment between two nodes for a given cost
|
||||
// class. Input are variable indices of nodes and the cost class.
|
||||
// Unlike GetArcCostForVehicle(), if cost_class is kNoCost, then the
|
||||
@@ -1078,7 +1099,7 @@ class RoutingModel {
|
||||
// of the cost that depend on the cost class will be omited. See the code
|
||||
// for details.
|
||||
int64 GetArcCostForClass(int64 from_index, int64 to_index,
|
||||
int64 /*CostClassIndex*/ cost_class_index);
|
||||
int64 /*CostClassIndex*/ cost_class_index) const;
|
||||
// Get the cost class index of the given vehicle.
|
||||
CostClassIndex GetCostClassIndexOfVehicle(int64 vehicle) const {
|
||||
DCHECK(closed_);
|
||||
@@ -1332,7 +1353,7 @@ class RoutingModel {
|
||||
void ComputeCostClasses(const RoutingSearchParameters& parameters);
|
||||
void ComputeVehicleClasses();
|
||||
int64 GetArcCostForClassInternal(int64 from_index, int64 to_index,
|
||||
CostClassIndex cost_class_index);
|
||||
CostClassIndex cost_class_index) const;
|
||||
void AppendHomogeneousArcCosts(const RoutingSearchParameters& parameters,
|
||||
int node_index,
|
||||
std::vector<IntVar*>* cost_elements);
|
||||
@@ -1468,7 +1489,7 @@ class RoutingModel {
|
||||
// - IsEnd(i) is true
|
||||
// - or nexts_[i] is bound and is_bound_to_end_[nexts_[i].Value()] is true.
|
||||
std::vector<IntVar*> is_bound_to_end_;
|
||||
RevSwitch is_bound_to_end_ct_added_;
|
||||
mutable RevSwitch is_bound_to_end_ct_added_;
|
||||
// Dimensions
|
||||
absl::flat_hash_map<std::string, DimensionIndex> dimension_name_to_index_;
|
||||
gtl::ITIVector<DimensionIndex, RoutingDimension*> dimensions_;
|
||||
@@ -1498,7 +1519,7 @@ class RoutingModel {
|
||||
#endif // SWIG
|
||||
bool costs_are_homogeneous_across_vehicles_;
|
||||
bool cache_callbacks_;
|
||||
std::vector<CostCacheElement> cost_cache_; // Index by source index.
|
||||
mutable std::vector<CostCacheElement> cost_cache_; // Index by source index.
|
||||
std::vector<VehicleClassIndex> vehicle_class_index_of_vehicle_;
|
||||
#ifndef SWIG
|
||||
gtl::ITIVector<VehicleClassIndex, VehicleClass> vehicle_classes_;
|
||||
@@ -1542,10 +1563,13 @@ class RoutingModel {
|
||||
temporal_incompatible_types_per_type_index_;
|
||||
bool has_temporal_type_incompatibilities_;
|
||||
|
||||
std::vector<std::vector<absl::flat_hash_set<int> > >
|
||||
same_vehicle_required_type_alternatives_per_type_index_;
|
||||
bool has_same_vehicle_type_requirements_;
|
||||
std::vector<std::vector<absl::flat_hash_set<int> > >
|
||||
temporal_required_type_alternatives_per_type_index_;
|
||||
absl::flat_hash_set<int> trivially_infeasible_visit_types_;
|
||||
bool has_temporal_type_requirements_;
|
||||
absl::flat_hash_set<int> trivially_infeasible_visit_types_;
|
||||
// clang-format on
|
||||
int num_visit_types_;
|
||||
// Two indices are equivalent if they correspond to the same node (as given to
|
||||
@@ -1655,6 +1679,18 @@ class DisjunctivePropagator {
|
||||
std::vector<bool> is_preemptible;
|
||||
std::vector<const SortedDisjointIntervalList*> forbidden_intervals;
|
||||
std::vector<std::pair<int64, int64>> distance_duration;
|
||||
|
||||
void Clear() {
|
||||
start_min.clear();
|
||||
start_max.clear();
|
||||
duration_min.clear();
|
||||
duration_max.clear();
|
||||
end_min.clear();
|
||||
end_max.clear();
|
||||
is_preemptible.clear();
|
||||
forbidden_intervals.clear();
|
||||
distance_duration.clear();
|
||||
}
|
||||
};
|
||||
|
||||
// Computes new bounds for all tasks, returns false if infeasible.
|
||||
@@ -1685,6 +1721,19 @@ class DisjunctivePropagator {
|
||||
std::vector<int> event_of_task_;
|
||||
std::vector<int> nonchain_tasks_by_start_max_;
|
||||
};
|
||||
|
||||
void AppendTasksFromPath(const std::vector<int64>& path,
|
||||
const std::vector<int64>& min_travels,
|
||||
const std::vector<int64>& max_travels,
|
||||
const std::vector<int64>& pre_travels,
|
||||
const std::vector<int64>& post_travels,
|
||||
const RoutingDimension& dimension,
|
||||
DisjunctivePropagator::Tasks* tasks);
|
||||
void AppendTasksFromIntervals(const std::vector<IntervalVar*>& intervals,
|
||||
DisjunctivePropagator::Tasks* tasks);
|
||||
void FillPathEvaluation(const std::vector<int64>& path,
|
||||
const RoutingModel::TransitCallback2& evaluator,
|
||||
std::vector<int64>* values);
|
||||
#endif // !defined(SWIG)
|
||||
|
||||
// GlobalVehicleBreaksConstraint ensures breaks constraints are enforced on
|
||||
@@ -1697,11 +1746,6 @@ class DisjunctivePropagator {
|
||||
// between two consecutive nodes of a route to do transit and vehicle breaks,
|
||||
// i.e. if Next(nodeA) = nodeB, CumulVar(nodeA) = tA and CumulVar(nodeB) = tB,
|
||||
// then SlackVar(nodeA) >= sum_{breaks \subseteq [tA, tB)} duration(break).
|
||||
// TODO(user): This does not enforce vehicle breaks to be nonoverlapping,
|
||||
// and supposes travel/service times to be feasible (e.g. with a PathCumul).
|
||||
// This is probably the desired behaviour, because vehicle breaks will most
|
||||
// likely be constrained with precedence relations that are stronger than
|
||||
// a resource constraint.
|
||||
class GlobalVehicleBreaksConstraint : public Constraint {
|
||||
public:
|
||||
explicit GlobalVehicleBreaksConstraint(const RoutingDimension* dimension);
|
||||
@@ -1717,31 +1761,45 @@ class GlobalVehicleBreaksConstraint : public Constraint {
|
||||
const RoutingModel* model_;
|
||||
const RoutingDimension* const dimension_;
|
||||
std::vector<Demon*> vehicle_demons_;
|
||||
std::vector<int64> path_;
|
||||
|
||||
// Sets path_ to be the longest sequence such that
|
||||
// _ path_[0] is the start of the vehicle
|
||||
// _ Next(path_[i-1]) is Bound() and has value path_[i],
|
||||
// followed by the end of the vehicle if the last node was not an end.
|
||||
void FillPartialPathOfVehicle(int vehicle);
|
||||
void FillPathTravels(const std::vector<int64>& path);
|
||||
|
||||
// This translates pruning information to solver variables.
|
||||
// If constructed with an IntervalVar*, it follows the usual semantics of
|
||||
// IntervalVars. If constructed with an IntVar*, before_start and after_start,
|
||||
// operations are translated to simulate an interval that starts at
|
||||
// start - before_start and ends and start + after_start.
|
||||
// If constructed with nothing, the TaskTranslator will do nothing.
|
||||
// This class should have been an interface + subclasses,
|
||||
// but that would force pointers in the user's task vector,
|
||||
// which means dynamic allocation. Here such a vector's reserved size will
|
||||
// adjust to usage and eventually no more dynamic allocation will be made.
|
||||
// which means dynamic allocation. With this union-like structure,
|
||||
// a vector's reserved size will adjust to usage and eventually no more
|
||||
// dynamic allocation will be made.
|
||||
class TaskTranslator {
|
||||
public:
|
||||
TaskTranslator(IntVar* start, int64 duration_min, int64 group_delay)
|
||||
TaskTranslator(IntVar* start, int64 before_start, int64 after_start)
|
||||
: start_(start),
|
||||
duration_min_(duration_min),
|
||||
group_delay_(group_delay) {}
|
||||
before_start_(before_start),
|
||||
after_start_(after_start) {}
|
||||
explicit TaskTranslator(IntervalVar* interval) : interval_(interval) {}
|
||||
TaskTranslator() {}
|
||||
|
||||
void SetStartMin(int64 value) {
|
||||
if (start_ != nullptr) {
|
||||
start_->SetMin(CapAdd(group_delay_, value));
|
||||
start_->SetMin(CapAdd(before_start_, value));
|
||||
} else if (interval_ != nullptr) {
|
||||
interval_->SetStartMin(value);
|
||||
}
|
||||
}
|
||||
void SetStartMax(int64 value) {
|
||||
if (start_ != nullptr) {
|
||||
start_->SetMax(CapAdd(group_delay_, value));
|
||||
start_->SetMax(CapAdd(before_start_, value));
|
||||
} else if (interval_ != nullptr) {
|
||||
interval_->SetStartMax(value);
|
||||
}
|
||||
@@ -1753,14 +1811,14 @@ class GlobalVehicleBreaksConstraint : public Constraint {
|
||||
}
|
||||
void SetEndMin(int64 value) {
|
||||
if (start_ != nullptr) {
|
||||
start_->SetMin(CapSub(value, duration_min_));
|
||||
start_->SetMin(CapSub(value, after_start_));
|
||||
} else if (interval_ != nullptr) {
|
||||
interval_->SetEndMin(value);
|
||||
}
|
||||
}
|
||||
void SetEndMax(int64 value) {
|
||||
if (start_ != nullptr) {
|
||||
start_->SetMax(value - duration_min_);
|
||||
start_->SetMax(CapSub(value, after_start_));
|
||||
} else if (interval_ != nullptr) {
|
||||
interval_->SetEndMax(value);
|
||||
}
|
||||
@@ -1768,8 +1826,8 @@ class GlobalVehicleBreaksConstraint : public Constraint {
|
||||
|
||||
private:
|
||||
IntVar* start_ = nullptr;
|
||||
int64 duration_min_;
|
||||
int64 group_delay_;
|
||||
int64 before_start_;
|
||||
int64 after_start_;
|
||||
IntervalVar* interval_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -1780,8 +1838,11 @@ class GlobalVehicleBreaksConstraint : public Constraint {
|
||||
DisjunctivePropagator disjunctive_propagator_;
|
||||
DisjunctivePropagator::Tasks tasks_;
|
||||
|
||||
std::vector<IntVar*> cumuls_;
|
||||
std::vector<int64> fixed_transits_;
|
||||
// Fields used to help build tasks_ at each propagation.
|
||||
std::vector<int64> min_travel_;
|
||||
std::vector<int64> max_travel_;
|
||||
std::vector<int64> pre_travel_;
|
||||
std::vector<int64> post_travel_;
|
||||
};
|
||||
|
||||
class TypeRegulationsChecker {
|
||||
@@ -1806,7 +1867,9 @@ class TypeRegulationsChecker {
|
||||
int GetNonDeliveredCount(int type) const;
|
||||
|
||||
virtual bool HasRegulationsToCheck() const = 0;
|
||||
virtual bool CheckTypeRegulations(int type) const = 0;
|
||||
virtual void InitializeCheck() {}
|
||||
virtual bool CheckTypeRegulations(int type) = 0;
|
||||
virtual bool FinalizeCheck() const { return true; }
|
||||
|
||||
const RoutingModel& model_;
|
||||
|
||||
@@ -1824,7 +1887,7 @@ class TypeIncompatibilityChecker : public TypeRegulationsChecker {
|
||||
|
||||
private:
|
||||
bool HasRegulationsToCheck() const override;
|
||||
bool CheckTypeRegulations(int type) const override;
|
||||
bool CheckTypeRegulations(int type) override;
|
||||
// NOTE(user): As temporal incompatibilities are always verified with this
|
||||
// checker, we only store 1 boolean indicating whether or not hard
|
||||
// incompatibilities are also verified.
|
||||
@@ -1840,7 +1903,13 @@ class TypeRequirementChecker : public TypeRegulationsChecker {
|
||||
|
||||
private:
|
||||
bool HasRegulationsToCheck() const override;
|
||||
bool CheckTypeRegulations(int type) const override;
|
||||
void InitializeCheck() override {
|
||||
types_with_same_vehicle_requirements_on_route_.clear();
|
||||
}
|
||||
bool CheckTypeRegulations(int type) override;
|
||||
bool FinalizeCheck() const override;
|
||||
|
||||
absl::flat_hash_set<int> types_with_same_vehicle_requirements_on_route_;
|
||||
};
|
||||
|
||||
// The following constraint ensures that incompatibilities and requirements
|
||||
@@ -1856,9 +1925,10 @@ class TypeRequirementChecker : public TypeRegulationsChecker {
|
||||
// p1 --> d1 --> n --> p2 --> d2 is acceptable, whereas any configurations
|
||||
// with p1 --> p2 --> d1 --> ..., or p1 --> n --> d1 --> ... is not feasible.
|
||||
//
|
||||
// It also verifies temporal type requirements.
|
||||
// In the above example, if T1 is a requirement for T2, p2 must be visited
|
||||
// between p1 and d1.
|
||||
// It also verifies same-vehicle and temporal type requirements.
|
||||
// In the above example, if T1 is a requirement for T2:
|
||||
// - For a same-vehicle requirement, p1/d1 must be on the same vehicle as p2/d2.
|
||||
// - For a temporal requirement, p2 must be visited between p1 and d1.
|
||||
class TypeRegulationsConstraint : public Constraint {
|
||||
public:
|
||||
explicit TypeRegulationsConstraint(const RoutingModel& model);
|
||||
@@ -1875,6 +1945,37 @@ class TypeRegulationsConstraint : public Constraint {
|
||||
TypeRequirementChecker requirement_checker_;
|
||||
std::vector<Demon*> vehicle_demons_;
|
||||
};
|
||||
#if !defined SWIG
|
||||
// A structure meant to store soft bounds and associated violation constants.
|
||||
// It is 'Simple' because it has one BoundCost per element,
|
||||
// in contrast to 'Multiple'. Design notes:
|
||||
// - it is meant to store model information to be shared through pointers,
|
||||
// so it disallows copy and assign to avoid accidental duplication.
|
||||
// - it keeps soft bounds as an array of structs to help cache,
|
||||
// because code that uses such bounds typically use both bound and cost.
|
||||
// - soft bounds are named pairs, prevents some mistakes.
|
||||
// - using operator[] to access elements is not interesting,
|
||||
// because the structure will be accessed through pointers, moreover having
|
||||
// to type bound_cost reminds the user of the order if they do a copy
|
||||
// assignment of the element.
|
||||
class SimpleBoundCosts {
|
||||
public:
|
||||
struct BoundCost {
|
||||
int64 bound;
|
||||
int64 cost;
|
||||
};
|
||||
SimpleBoundCosts(int num_bounds, BoundCost default_bound_cost)
|
||||
: bound_costs_(num_bounds, default_bound_cost) {}
|
||||
BoundCost& bound_cost(int element) { return bound_costs_[element]; }
|
||||
BoundCost bound_cost(int element) const { return bound_costs_[element]; }
|
||||
int Size() { return bound_costs_.size(); }
|
||||
SimpleBoundCosts(const SimpleBoundCosts&) = delete;
|
||||
SimpleBoundCosts operator=(const SimpleBoundCosts&) = delete;
|
||||
|
||||
private:
|
||||
std::vector<BoundCost> bound_costs_;
|
||||
};
|
||||
#endif // !defined SWIG
|
||||
|
||||
// Dimensions represent quantities accumulated at nodes along the routes. They
|
||||
// represent quantities such as weights or volumes carried along the route, or
|
||||
@@ -2030,36 +2131,51 @@ class RoutingDimension {
|
||||
int64 GetCumulVarSoftLowerBoundCoefficient(int64 index) const;
|
||||
// Sets the breaks for a given vehicle. Breaks are represented by
|
||||
// IntervalVars. They may interrupt transits between nodes and increase
|
||||
// the value of corresponding slack variables. However a break interval cannot
|
||||
// overlap the transit interval of a node, which is
|
||||
// [CumulVar(node), CumulVar(node) + node_visit_transits[node]), i.e. the
|
||||
// break interval must either end before CumulVar(node) or start after
|
||||
// CumulVar(node) + node_visit_transits[node].
|
||||
// In the case where the route entails some group delay on a visit, the break
|
||||
// cannot overlap that delay either, so the time window it cannot overlap is
|
||||
// [CumulVar(node) - delay, CumulVar(node) + node_visit_transits[node]).
|
||||
// the value of corresponding slack variables.
|
||||
// A break may take place before the start of a vehicle, after the end of
|
||||
// a vehicle, or during a travel i -> j.
|
||||
// In that case, the interval [break.Start(), break.End()) must be a subset of
|
||||
// [CumulVar(i) + pre_travel(i, j), CumulVar(j) - post_travel(i, j)).
|
||||
// In other words, a break may not overlap any node n's visit, given by
|
||||
// [CumulVar(n) - post_travel(_, n), CumulVar(n) + pre_travel(n, _)).
|
||||
// This formula considers post_travel(_, start) and pre_travel(end, _) to be
|
||||
// 0; pre_travel will never be called on any (_, start) and post_travel will
|
||||
// never we called on any (end, _). If pre_travel_evaluator or
|
||||
// post_travel_evaluator is -1, it will be taken as a function that always
|
||||
// returns 0.
|
||||
// TODO(user): Remove if !defined when routing.i is repaired.
|
||||
#if !defined(SWIGPYTHON)
|
||||
void SetBreakIntervalsOfVehicle(std::vector<IntervalVar*> breaks, int vehicle,
|
||||
int pre_travel_evaluator,
|
||||
int post_travel_evaluator);
|
||||
#endif // !defined(SWIGPYTHON)
|
||||
|
||||
// Deprecated, sets pre_travel(i, j) = node_visit_transit[i].
|
||||
void SetBreakIntervalsOfVehicle(std::vector<IntervalVar*> breaks, int vehicle,
|
||||
std::vector<int64> node_visit_transits);
|
||||
|
||||
// With breaks supposed to be consecutive, this forces the distance between
|
||||
// breaks of size at least minimum_break_duration to be at least distance.
|
||||
// This supposes that the time until route start and after route end are
|
||||
// infinite breaks.
|
||||
void SetBreakDistanceDurationOfVehicle(int64 distance, int64 duration,
|
||||
int vehicle);
|
||||
// Sets up vehicle_break_intervals_, vehicle_break_distance_duration_,
|
||||
// pre_travel_evaluators and post_travel_evaluators.
|
||||
void InitializeBreaks();
|
||||
// Returns true if any break interval or break distance was defined.
|
||||
bool HasBreakConstraints() const;
|
||||
#if !defined(SWIGPYTHON)
|
||||
// Deprecated, sets pre_travel(i, j) = node_visit_transit[i]
|
||||
// and post_travel(i, j) = group_delays(i, j).
|
||||
void SetBreakIntervalsOfVehicle(
|
||||
std::vector<IntervalVar*> breaks, int vehicle,
|
||||
std::vector<int64> node_visit_transits,
|
||||
std::function<int64(int64 from_index, int64 to_index)> group_delay);
|
||||
// Returns true if the vehicle has break intervals.
|
||||
bool VehicleHasBreakIntervals(int vehicle) const;
|
||||
// Returns true iff some break distance constraint was set for this vehicle.
|
||||
bool HasBreakDistanceDurationOfVehicle(int vehicle) const;
|
||||
std::function<int64(int64, int64)> group_delays);
|
||||
|
||||
// Returns the break intervals set by SetBreakIntervalsOfVehicle().
|
||||
const std::vector<IntervalVar*>& GetBreakIntervalsOfVehicle(
|
||||
int vehicle) const;
|
||||
// Returns the amount of visit transit set by SetBreakIntervalsOfVehicle().
|
||||
const std::vector<int64>& GetNodeVisitTransitsOfVehicle(int vehicle) const;
|
||||
// Returns the pairs (distance, duration) specified by break distance
|
||||
// constraints.
|
||||
// clang-format off
|
||||
@@ -2067,6 +2183,8 @@ class RoutingDimension {
|
||||
GetBreakDistanceDurationOfVehicle(int vehicle) const;
|
||||
// clang-format on
|
||||
#endif // !defined(SWIGPYTHON)
|
||||
int GetPreTravelEvaluatorOfVehicle(int vehicle) const;
|
||||
int GetPostTravelEvaluatorOfVehicle(int vehicle) const;
|
||||
|
||||
// Returns the parent in the dependency tree if any or nullptr otherwise.
|
||||
const RoutingDimension* base_dimension() const { return base_dimension_; }
|
||||
@@ -2146,11 +2264,6 @@ class RoutingDimension {
|
||||
return global_span_cost_coefficient_;
|
||||
}
|
||||
|
||||
int64 GetGroupDelay(int vehicle, int64 from_index, int64 to_index) const {
|
||||
DCHECK_LT(vehicle, vehicle_group_delays_.size());
|
||||
return vehicle_group_delays_[vehicle](from_index, to_index);
|
||||
}
|
||||
|
||||
int64 GetGlobalOptimizerOffset() const {
|
||||
DCHECK_GE(global_optimizer_offset_, 0);
|
||||
return global_optimizer_offset_;
|
||||
@@ -2162,6 +2275,26 @@ class RoutingDimension {
|
||||
DCHECK_GE(local_optimizer_offset_for_vehicle_[vehicle], 0);
|
||||
return local_optimizer_offset_for_vehicle_[vehicle];
|
||||
}
|
||||
#if !defined SWIG
|
||||
// If the span of vehicle on this dimension is larger than bound,
|
||||
// the cost will be increased by cost * (span - bound).
|
||||
void SetSoftSpanUpperBoundForVehicle(SimpleBoundCosts::BoundCost bound_cost,
|
||||
int vehicle) {
|
||||
if (!HasSoftSpanUpperBounds()) {
|
||||
vehicle_soft_span_upper_bound_ = absl::make_unique<SimpleBoundCosts>(
|
||||
model_->vehicles(), SimpleBoundCosts::BoundCost{kint64max, 0});
|
||||
}
|
||||
vehicle_soft_span_upper_bound_->bound_cost(vehicle) = bound_cost;
|
||||
}
|
||||
bool HasSoftSpanUpperBounds() const {
|
||||
return vehicle_soft_span_upper_bound_ != nullptr;
|
||||
}
|
||||
SimpleBoundCosts::BoundCost GetSoftSpanUpperBoundForVehicle(
|
||||
int vehicle) const {
|
||||
DCHECK(HasSoftSpanUpperBounds());
|
||||
return vehicle_soft_span_upper_bound_->bound_cost(vehicle);
|
||||
}
|
||||
#endif // !defined SWIG
|
||||
|
||||
private:
|
||||
struct SoftBound {
|
||||
@@ -2252,14 +2385,17 @@ class RoutingDimension {
|
||||
pickup_to_delivery_limits_per_pair_index_;
|
||||
|
||||
// Used if some vehicle has breaks in this dimension, typically time.
|
||||
bool break_constraints_are_initialized_ = false;
|
||||
// clang-format off
|
||||
std::vector<std::vector<IntervalVar*> > vehicle_break_intervals_;
|
||||
std::vector<std::vector<int64> > vehicle_node_visit_transits_;
|
||||
std::vector<std::function<int64(int64 from_index, int64 to_index)> >
|
||||
vehicle_group_delays_;
|
||||
std::vector<std::vector<std::pair<int64, int64> > >
|
||||
vehicle_break_distance_duration_;
|
||||
// clang-format on
|
||||
// For each vehicle, stores the part of travel that is made directly
|
||||
// after (before) the departure (arrival) node of the travel.
|
||||
// These parts of the travel are non-interruptible, in particular by a break.
|
||||
std::vector<int> vehicle_pre_travel_evaluators_;
|
||||
std::vector<int> vehicle_post_travel_evaluators_;
|
||||
|
||||
std::vector<IntVar*> slacks_;
|
||||
std::vector<IntVar*> dependent_transits_;
|
||||
@@ -2273,7 +2409,8 @@ class RoutingDimension {
|
||||
const std::string name_;
|
||||
int64 global_optimizer_offset_;
|
||||
std::vector<int64> local_optimizer_offset_for_vehicle_;
|
||||
|
||||
// nullptr if not defined.
|
||||
std::unique_ptr<SimpleBoundCosts> vehicle_soft_span_upper_bound_;
|
||||
friend class RoutingModel;
|
||||
friend class RoutingModelInspector;
|
||||
|
||||
@@ -3001,6 +3138,12 @@ class ChristofidesFilteredDecisionBuilder
|
||||
}
|
||||
};
|
||||
|
||||
// Attempts to solve the model using the cp-sat solver. As of 5/2019, will solve
|
||||
// the TSP corresponding to the model if it has a single vehicle. Therefore the
|
||||
// resulting solution might not actually be feasible. Will return false if a
|
||||
// solution could not be found.
|
||||
bool SolveModelWithSat(RoutingModel* model, Assignment* solution);
|
||||
|
||||
// Generic path-based filter class.
|
||||
|
||||
class BasePathFilter : public IntVarLocalSearchFilter {
|
||||
@@ -3101,14 +3244,14 @@ IntVarLocalSearchFilter* MakeTypeRegulationsFilter(
|
||||
const RoutingModel& routing_model);
|
||||
std::vector<IntVarLocalSearchFilter*> MakeCumulFilters(
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback, bool filter_objective);
|
||||
Solver::ObjectiveWatcher objective_callback, bool filter_objective_cost);
|
||||
IntVarLocalSearchFilter* MakePathCumulFilter(
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback,
|
||||
bool propagate_own_objective_value);
|
||||
bool propagate_own_objective_value, bool filter_objective_cost);
|
||||
IntVarLocalSearchFilter* MakeGlobalLPCumulFilter(
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback);
|
||||
Solver::ObjectiveWatcher objective_callback, bool filter_objective_cost);
|
||||
IntVarLocalSearchFilter* MakePickupDeliveryFilter(
|
||||
const RoutingModel& routing_model, const RoutingModel::IndexPairs& pairs,
|
||||
const std::vector<RoutingModel::PickupAndDeliveryPolicy>& vehicle_policies);
|
||||
|
||||
@@ -253,6 +253,8 @@ void AddLocalSearchNeighborhoodOperatorsFromFlags(
|
||||
void SetSearchLimitsFromFlags(RoutingSearchParameters* parameters) {
|
||||
CHECK(parameters != nullptr);
|
||||
parameters->set_use_depth_first_search(FLAGS_routing_dfs);
|
||||
parameters->set_use_cp(BOOL_TRUE);
|
||||
parameters->set_use_cp_sat(BOOL_FALSE);
|
||||
parameters->set_optimization_step(FLAGS_routing_optimization_step);
|
||||
parameters->set_number_of_solutions_to_collect(
|
||||
FLAGS_routing_number_of_solutions_to_collect);
|
||||
|
||||
@@ -56,6 +56,7 @@ bool SetVariableBounds(glop::LinearProgram* linear_program,
|
||||
// lp_min > lp_max case, so we must detect infeasibility here.
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LocalDimensionCumulOptimizer::LocalDimensionCumulOptimizer(
|
||||
@@ -106,10 +107,19 @@ bool LocalDimensionCumulOptimizer::ComputeRouteCumuls(
|
||||
lp_solver_[vehicle].get(), optimal_cumuls, nullptr, nullptr);
|
||||
}
|
||||
|
||||
bool LocalDimensionCumulOptimizer::ComputePackedRouteCumuls(
|
||||
int vehicle, const std::function<int64(int64)>& next_accessor,
|
||||
std::vector<int64>* packed_cumuls) {
|
||||
return optimizer_core_.OptimizeAndPackSingleRoute(
|
||||
vehicle, next_accessor, linear_program_[vehicle].get(),
|
||||
lp_solver_[vehicle].get(), packed_cumuls);
|
||||
}
|
||||
|
||||
bool DimensionCumulOptimizerCore::OptimizeSingleRoute(
|
||||
int vehicle, const std::function<int64(int64)>& next_accessor,
|
||||
glop::LinearProgram* linear_program, glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values, int64* cost, int64* transit_cost) {
|
||||
std::vector<int64>* cumul_values, int64* cost, int64* transit_cost,
|
||||
bool clear_lp) {
|
||||
InitOptimizer(linear_program);
|
||||
|
||||
const RoutingModel* const model = dimension()->model();
|
||||
@@ -133,14 +143,17 @@ bool DimensionCumulOptimizerCore::OptimizeSingleRoute(
|
||||
*cost = CapAdd(cost_offset, std::round(lp_solver->GetObjectiveValue()));
|
||||
}
|
||||
|
||||
linear_program->Clear();
|
||||
if (clear_lp) {
|
||||
linear_program->Clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DimensionCumulOptimizerCore::Optimize(
|
||||
const std::function<int64(int64)>& next_accessor,
|
||||
glop::LinearProgram* linear_program, glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values, int64* cost, int64* transit_cost) {
|
||||
std::vector<int64>* cumul_values, int64* cost, int64* transit_cost,
|
||||
bool clear_lp) {
|
||||
InitOptimizer(linear_program);
|
||||
|
||||
// If both "cumul_values" and "cost" parameters are null, we don't try to
|
||||
@@ -186,7 +199,9 @@ bool DimensionCumulOptimizerCore::Optimize(
|
||||
CapAdd(std::round(lp_solver->GetObjectiveValue()), total_cost_offset);
|
||||
}
|
||||
|
||||
linear_program->Clear();
|
||||
if (clear_lp) {
|
||||
linear_program->Clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -194,28 +209,54 @@ bool DimensionCumulOptimizerCore::OptimizeAndPack(
|
||||
const std::function<int64(int64)>& next_accessor,
|
||||
glop::LinearProgram* linear_program, glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values) {
|
||||
InitOptimizer(linear_program);
|
||||
|
||||
const int64 cumul_offset = dimension_->GetGlobalOptimizerOffset();
|
||||
const RoutingModel* model = dimension()->model();
|
||||
bool has_vehicles_being_optimized = false;
|
||||
for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
|
||||
const bool optimize_vehicle_costs =
|
||||
!model->IsEnd(next_accessor(model->Start(vehicle))) ||
|
||||
model->AreEmptyRouteCostsConsideredForVehicle(vehicle);
|
||||
if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
|
||||
optimize_vehicle_costs, linear_program,
|
||||
nullptr, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
has_vehicles_being_optimized |= optimize_vehicle_costs;
|
||||
}
|
||||
SetGlobalConstraints(has_vehicles_being_optimized, linear_program);
|
||||
|
||||
if (!FinalizeAndSolve(linear_program, lp_solver)) {
|
||||
// Note: We pass a non-nullptr cost to the Optimize() method so the costs are
|
||||
// optimized by the LP.
|
||||
int64 cost = 0;
|
||||
if (!Optimize(next_accessor, linear_program, lp_solver,
|
||||
/*cumul_values=*/nullptr, &cost, /*transit_cost=*/nullptr,
|
||||
/*clear_lp=*/false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<int> vehicles(dimension()->model()->vehicles());
|
||||
std::iota(vehicles.begin(), vehicles.end(), 0);
|
||||
if (!PackRoutes(std::move(vehicles), linear_program, lp_solver)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCumulValuesFromLP(index_to_cumul_variable_,
|
||||
dimension_->GetGlobalOptimizerOffset(), *lp_solver,
|
||||
cumul_values);
|
||||
linear_program->Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DimensionCumulOptimizerCore::OptimizeAndPackSingleRoute(
|
||||
int vehicle, const std::function<int64(int64)>& next_accessor,
|
||||
glop::LinearProgram* linear_program, glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values) {
|
||||
// Note: We pass a non-nullptr cost to the OptimizeSingleRoute() method so the
|
||||
// costs are optimized by the LP.
|
||||
int64 cost = 0;
|
||||
if (!OptimizeSingleRoute(vehicle, next_accessor, linear_program, lp_solver,
|
||||
/*cumul_values=*/nullptr, &cost,
|
||||
/*transit_cost=*/nullptr,
|
||||
/*clear_lp=*/false) ||
|
||||
!PackRoutes({vehicle}, linear_program, lp_solver)) {
|
||||
return false;
|
||||
}
|
||||
SetCumulValuesFromLP(current_route_cumul_variables_,
|
||||
dimension_->GetLocalOptimizerOffsetForVehicle(vehicle),
|
||||
*lp_solver, cumul_values);
|
||||
linear_program->Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DimensionCumulOptimizerCore::PackRoutes(
|
||||
std::vector<int> vehicles, glop::LinearProgram* linear_program,
|
||||
glop::LPSolver* lp_solver) {
|
||||
const RoutingModel* model = dimension_->model();
|
||||
|
||||
// Minimize the route end times without increasing the cost.
|
||||
glop::RowIndex objective_ct = linear_program->CreateNewConstraint();
|
||||
linear_program->SetConstraintBounds(objective_ct, 0,
|
||||
@@ -231,7 +272,7 @@ bool DimensionCumulOptimizerCore::OptimizeAndPack(
|
||||
linear_program->SetObjectiveCoefficient(variable, 0);
|
||||
}
|
||||
}
|
||||
for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
|
||||
for (int vehicle : vehicles) {
|
||||
linear_program->SetObjectiveCoefficient(
|
||||
index_to_cumul_variable_[model->End(vehicle)], 1);
|
||||
}
|
||||
@@ -242,7 +283,7 @@ bool DimensionCumulOptimizerCore::OptimizeAndPack(
|
||||
|
||||
// Maximize the route start times without increasing the cost or the route end
|
||||
// times.
|
||||
for (int vehicle = 0; vehicle < model->vehicles(); vehicle++) {
|
||||
for (int vehicle : vehicles) {
|
||||
const glop::ColIndex end_cumul_var =
|
||||
index_to_cumul_variable_[model->End(vehicle)];
|
||||
// end_cumul_var <= lp_solver.variable_values()[end_cumul_var]
|
||||
@@ -259,9 +300,6 @@ bool DimensionCumulOptimizerCore::OptimizeAndPack(
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCumulValuesFromLP(index_to_cumul_variable_, cumul_offset, *lp_solver,
|
||||
cumul_values);
|
||||
linear_program->Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,18 +40,23 @@ class DimensionCumulOptimizerCore {
|
||||
glop::LinearProgram* linear_program,
|
||||
glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values, int64* cost,
|
||||
int64* transit_cost);
|
||||
int64* transit_cost, bool clear_lp = true);
|
||||
|
||||
bool Optimize(const std::function<int64(int64)>& next_accessor,
|
||||
glop::LinearProgram* linear_program, glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values, int64* cost,
|
||||
int64* transit_cost);
|
||||
int64* transit_cost, bool clear_lp = true);
|
||||
|
||||
bool OptimizeAndPack(const std::function<int64(int64)>& next_accessor,
|
||||
glop::LinearProgram* linear_program,
|
||||
glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values);
|
||||
|
||||
bool OptimizeAndPackSingleRoute(
|
||||
int vehicle, const std::function<int64(int64)>& next_accessor,
|
||||
glop::LinearProgram* linear_program, glop::LPSolver* lp_solver,
|
||||
std::vector<int64>* cumul_values);
|
||||
|
||||
const RoutingDimension* dimension() const { return dimension_; }
|
||||
|
||||
private:
|
||||
@@ -85,6 +90,16 @@ class DimensionCumulOptimizerCore {
|
||||
int64 offset, const glop::LPSolver& lp_solver,
|
||||
std::vector<int64>* cumul_values);
|
||||
|
||||
// This function packs the routes of the given vehicles while keeping the cost
|
||||
// of the LP lower than its current (supposed optimal) objective value.
|
||||
// It does so by setting the current objective variables' coefficient to 0 and
|
||||
// setting the coefficient of the route ends to 1, to first minimize the route
|
||||
// ends' cumuls, and then maximizes the starts' cumuls without increasing the
|
||||
// ends.
|
||||
bool PackRoutes(std::vector<int> vehicles,
|
||||
glop::LinearProgram* linear_program,
|
||||
glop::LPSolver* lp_solver);
|
||||
|
||||
const RoutingDimension* const dimension_;
|
||||
std::vector<glop::ColIndex> current_route_cumul_variables_;
|
||||
std::vector<glop::ColIndex> index_to_cumul_variable_;
|
||||
@@ -101,6 +116,7 @@ class DimensionCumulOptimizerCore {
|
||||
class LocalDimensionCumulOptimizer {
|
||||
public:
|
||||
explicit LocalDimensionCumulOptimizer(const RoutingDimension* dimension);
|
||||
|
||||
// If feasible, computes the optimal cost of the route performed by a vehicle,
|
||||
// minimizing cumul soft lower and upper bound costs and vehicle span costs,
|
||||
// and stores it in "optimal_cost" (if not null).
|
||||
@@ -108,11 +124,13 @@ class LocalDimensionCumulOptimizer {
|
||||
bool ComputeRouteCumulCost(int vehicle,
|
||||
const std::function<int64(int64)>& next_accessor,
|
||||
int64* optimal_cost);
|
||||
|
||||
// Same as ComputeRouteCumulCost, but the cost computed does not contain
|
||||
// the part of the vehicle span cost due to fixed transits.
|
||||
bool ComputeRouteCumulCostWithoutFixedTransits(
|
||||
int vehicle, const std::function<int64(int64)>& next_accessor,
|
||||
int64* optimal_cost_without_transits);
|
||||
|
||||
// If feasible, computes the optimal cumul values of the route performed by a
|
||||
// vehicle, minimizing cumul soft lower and upper bound costs and vehicle span
|
||||
// costs, stores them in "optimal_cumuls" (if not null), and returns true.
|
||||
@@ -121,6 +139,13 @@ class LocalDimensionCumulOptimizer {
|
||||
const std::function<int64(int64)>& next_accessor,
|
||||
std::vector<int64>* optimal_cumuls);
|
||||
|
||||
// Similar to ComputeRouteCumuls, but also tries to pack the cumul values on
|
||||
// the route, such that the cost remains the same, the cumul of route end is
|
||||
// minimized, and then the cumul of the start of the route is maximized.
|
||||
bool ComputePackedRouteCumuls(
|
||||
int vehicle, const std::function<int64(int64)>& next_accessor,
|
||||
std::vector<int64>* packed_cumuls);
|
||||
|
||||
const RoutingDimension* dimension() const {
|
||||
return optimizer_core_.dimension();
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ RoutingSearchParameters DefaultRoutingSearchParameters() {
|
||||
"local_search_metaheuristic: AUTOMATIC "
|
||||
"guided_local_search_lambda_coefficient: 0.1 "
|
||||
"use_depth_first_search: false "
|
||||
"use_cp: BOOL_TRUE "
|
||||
"use_cp_sat: BOOL_FALSE "
|
||||
"optimization_step: 0.0 "
|
||||
"number_of_solutions_to_collect: 1 "
|
||||
// No "time_limit" by default.
|
||||
|
||||
@@ -331,6 +331,15 @@ message RoutingSearchParameters {
|
||||
// If true, the solver should use depth-first search rather than local search
|
||||
// to solve the problem.
|
||||
bool use_depth_first_search = 6;
|
||||
// If true, use the CP solver to find a solution. Either local or depth-first
|
||||
// search will be used depending on the value of use_depth_first_search. Will
|
||||
// be run after the CP-SAT solver if time is remaining (cf. use_cp_sat).
|
||||
OptionalBoolean use_cp = 28;
|
||||
// If true, use the CP-SAT solver to find a solution. If there is time
|
||||
// remaining normal CP local or depth-first search will be run if use_cp is
|
||||
// true.
|
||||
// As of 5/2019, only TSP models can be solved.
|
||||
OptionalBoolean use_cp_sat = 27;
|
||||
// Minimum step by which the solution must be improved in local search. 0
|
||||
// means "unspecified". If this value is fractional, it will get rounded to
|
||||
// the nearest integer.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// and local search filters.
|
||||
// TODO(user): Move all existing routing search code here.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
@@ -717,7 +718,7 @@ class TypeRegulationsFilter : public BasePathFilter {
|
||||
// The following vector is used to keep track of the type counts for hard
|
||||
// incompatibilities.
|
||||
std::vector<std::vector<int>> hard_incompatibility_type_counts_per_vehicle_;
|
||||
// Used to verify the temporal incompatibilities.
|
||||
// Used to verify the temporal incompatibilities and requirements.
|
||||
TypeIncompatibilityChecker temporal_incompatibility_checker_;
|
||||
TypeRequirementChecker requirement_checker_;
|
||||
};
|
||||
@@ -773,21 +774,23 @@ bool TypeRegulationsFilter::HardIncompatibilitiesRespected(int vehicle,
|
||||
int64 chain_end) {
|
||||
if (!routing_model_.HasHardTypeIncompatibilities()) return true;
|
||||
|
||||
std::vector<int> new_type_counts =
|
||||
const std::vector<int>& previous_type_counts =
|
||||
hard_incompatibility_type_counts_per_vehicle_[vehicle];
|
||||
const int num_types = new_type_counts.size();
|
||||
std::vector<int> types_to_check;
|
||||
|
||||
absl::flat_hash_map</*type*/ int, /*new_count*/ int> new_type_counts;
|
||||
absl::flat_hash_set<int> types_to_check;
|
||||
|
||||
// Go through the new nodes on the path and increment their type counts.
|
||||
int64 node = GetNext(chain_start);
|
||||
while (node != chain_end) {
|
||||
const int type = routing_model_.GetVisitType(node);
|
||||
if (type >= 0) {
|
||||
CHECK_LT(type, num_types);
|
||||
const int previous_count = new_type_counts[type]++;
|
||||
if (previous_count == 0) {
|
||||
DCHECK_LT(type, previous_type_counts.size());
|
||||
int& type_count = gtl::LookupOrInsert(&new_type_counts, type,
|
||||
previous_type_counts[type]);
|
||||
if (type_count++ == 0) {
|
||||
// New type on the route, mark to check its incompatibilities.
|
||||
types_to_check.push_back(type);
|
||||
types_to_check.insert(type);
|
||||
}
|
||||
}
|
||||
node = GetNext(node);
|
||||
@@ -799,8 +802,9 @@ bool TypeRegulationsFilter::HardIncompatibilitiesRespected(int vehicle,
|
||||
while (node != chain_end) {
|
||||
const int type = routing_model_.GetVisitType(node);
|
||||
if (type >= 0) {
|
||||
CHECK_LT(type, num_types);
|
||||
int& type_count = new_type_counts[type];
|
||||
DCHECK_LT(type, previous_type_counts.size());
|
||||
int& type_count = gtl::LookupOrInsert(&new_type_counts, type,
|
||||
previous_type_counts[type]);
|
||||
CHECK_GE(type_count, 1);
|
||||
type_count--;
|
||||
}
|
||||
@@ -811,7 +815,8 @@ bool TypeRegulationsFilter::HardIncompatibilitiesRespected(int vehicle,
|
||||
for (int type : types_to_check) {
|
||||
for (int incompatible_type :
|
||||
routing_model_.GetHardTypeIncompatibilitiesOfType(type)) {
|
||||
if (new_type_counts[incompatible_type] > 0) {
|
||||
if (gtl::FindWithDefault(new_type_counts, incompatible_type,
|
||||
previous_type_counts[incompatible_type]) > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -975,7 +980,8 @@ class PathCumulFilter : public BasePathFilter {
|
||||
PathCumulFilter(const RoutingModel& routing_model,
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback,
|
||||
bool propagate_own_objective_value);
|
||||
bool propagate_own_objective_value,
|
||||
bool filter_objective_cost);
|
||||
~PathCumulFilter() override {}
|
||||
std::string DebugString() const override {
|
||||
return "PathCumulFilter(" + name_ + ")";
|
||||
@@ -1074,7 +1080,8 @@ class PathCumulFilter : public BasePathFilter {
|
||||
}
|
||||
|
||||
bool FilterBreakCost(int vehicle) const {
|
||||
return dimension_.VehicleHasBreakIntervals(vehicle);
|
||||
return dimension_.HasBreakConstraints() &&
|
||||
!dimension_.GetBreakIntervalsOfVehicle(vehicle).empty();
|
||||
}
|
||||
|
||||
bool FilterCumulSoftBounds() const { return !cumul_soft_bounds_.empty(); }
|
||||
@@ -1089,15 +1096,17 @@ class PathCumulFilter : public BasePathFilter {
|
||||
// The DimensionCumulOptimizer is used to compute a more precise value of
|
||||
// the cost related to the cumul values (soft bounds and span costs).
|
||||
// Therefore, we only use the optimizer when the costs are actually used to
|
||||
// filter the solutions, i.e. when we propagate the cost (during local
|
||||
// search).
|
||||
// NOTE: The optimizer might eventually catch an infeasibility not caught
|
||||
// elsewhere in the filter. If that becomes the case, consider removing the
|
||||
// "CanPropagateObjectiveValue() &&" from the clause.
|
||||
return CanPropagateObjectiveValue() &&
|
||||
(dimension_.GetSpanCostCoefficientForVehicle(vehicle) > 0) &&
|
||||
!FilterCumulPiecewiseLinearCosts() &&
|
||||
(FilterCumulSoftBounds() || FilterCumulSoftLowerBounds());
|
||||
// filter the solutions, i.e. when filter_objective_cost_ is true.
|
||||
// NOTE: If and when the optimizer is used to filter infeasibilities not
|
||||
// caught elsewhere in the filter, we should use it regardless of
|
||||
// filter_objective_cost_.
|
||||
const bool has_span_cost =
|
||||
dimension_.GetSpanCostCoefficientForVehicle(vehicle) > 0;
|
||||
const bool has_soft_lower_bound = FilterCumulSoftLowerBounds();
|
||||
const bool has_soft_upper_bound = FilterCumulSoftBounds();
|
||||
return filter_objective_cost_ && !FilterCumulPiecewiseLinearCosts() &&
|
||||
(has_span_cost ^ has_soft_lower_bound ? has_soft_upper_bound
|
||||
: has_span_cost);
|
||||
}
|
||||
|
||||
int64 GetCumulPiecewiseLinearCost(int64 node, int64 cumul_value) const;
|
||||
@@ -1193,6 +1202,7 @@ class PathCumulFilter : public BasePathFilter {
|
||||
const std::string name_;
|
||||
|
||||
LocalDimensionCumulOptimizer optimizer_;
|
||||
const bool filter_objective_cost_;
|
||||
const bool propagate_own_objective_value_;
|
||||
|
||||
bool lns_detected_;
|
||||
@@ -1201,7 +1211,8 @@ class PathCumulFilter : public BasePathFilter {
|
||||
PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback,
|
||||
bool propagate_own_objective_value)
|
||||
bool propagate_own_objective_value,
|
||||
bool filter_objective_cost)
|
||||
: BasePathFilter(routing_model.Nexts(), dimension.cumuls().size(),
|
||||
std::move(objective_callback)),
|
||||
routing_model_(routing_model),
|
||||
@@ -1227,6 +1238,7 @@ PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
|
||||
delta_nodes_with_precedences_and_changed_cumul_(routing_model.Size()),
|
||||
name_(dimension.name()),
|
||||
optimizer_(&dimension),
|
||||
filter_objective_cost_(filter_objective_cost),
|
||||
propagate_own_objective_value_(propagate_own_objective_value),
|
||||
lns_detected_(false) {
|
||||
for (const int64 upper_bound : vehicle_span_upper_bounds_) {
|
||||
@@ -1889,26 +1901,25 @@ int64 PathCumulFilter::ComputePathMaxStartFromEndCumul(
|
||||
IntVarLocalSearchFilter* MakePathCumulFilter(
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback,
|
||||
bool propagate_own_objective_value) {
|
||||
bool propagate_own_objective_value, bool filter_objective_cost) {
|
||||
RoutingModel& model = *dimension.model();
|
||||
return model.solver()->RevAlloc(new PathCumulFilter(
|
||||
model, dimension, objective_callback, propagate_own_objective_value));
|
||||
model, dimension, objective_callback, propagate_own_objective_value,
|
||||
filter_objective_cost));
|
||||
}
|
||||
|
||||
std::vector<IntVarLocalSearchFilter*> MakeCumulFilters(
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback, bool filter_objective) {
|
||||
Solver::ObjectiveWatcher objective_callback, bool filter_objective_cost) {
|
||||
std::vector<IntVarLocalSearchFilter*> filters;
|
||||
if (!dimension.GetNodePrecedences().empty() ||
|
||||
(filter_objective && dimension.global_span_cost_coefficient() > 0)) {
|
||||
IntVarLocalSearchFilter* lp_cumul_filter =
|
||||
MakeGlobalLPCumulFilter(dimension, objective_callback);
|
||||
(filter_objective_cost && dimension.global_span_cost_coefficient() > 0)) {
|
||||
IntVarLocalSearchFilter* lp_cumul_filter = MakeGlobalLPCumulFilter(
|
||||
dimension, objective_callback, filter_objective_cost);
|
||||
filters.push_back(lp_cumul_filter);
|
||||
if (filter_objective) {
|
||||
objective_callback = [lp_cumul_filter](int64 value) {
|
||||
return lp_cumul_filter->InjectObjectiveValue(value);
|
||||
};
|
||||
}
|
||||
objective_callback = [lp_cumul_filter](int64 value) {
|
||||
return lp_cumul_filter->InjectObjectiveValue(value);
|
||||
};
|
||||
}
|
||||
// NOTE: We always add the PathCumulFilter to filter each route's feasibility
|
||||
// separately to try and cut bad decisions earlier in the search, but we don't
|
||||
@@ -1917,46 +1928,46 @@ std::vector<IntVarLocalSearchFilter*> MakeCumulFilters(
|
||||
|
||||
for (const int64 upper_bound : dimension.vehicle_span_upper_bounds()) {
|
||||
if (upper_bound != kint64max) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
for (const int64 coefficient : dimension.vehicle_span_cost_coefficients()) {
|
||||
if (coefficient != 0) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
for (const IntVar* const slack : dimension.slacks()) {
|
||||
if (slack->Min() > 0) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
const std::vector<IntVar*>& cumuls = dimension.cumuls();
|
||||
for (int i = 0; i < cumuls.size(); ++i) {
|
||||
if (dimension.HasCumulVarSoftUpperBound(i)) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
if (dimension.HasCumulVarSoftLowerBound(i)) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
if (dimension.HasCumulVarPiecewiseLinearCost(i)) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
IntVar* const cumul_var = cumuls[i];
|
||||
@@ -1964,14 +1975,15 @@ std::vector<IntVarLocalSearchFilter*> MakeCumulFilters(
|
||||
if (!dimension.model()->IsEnd(i)) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
propagate_path_cumul_filter_objective_value,
|
||||
filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
if (dimension.forbidden_intervals()[i].NumIntervals() > 0) {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
@@ -1979,9 +1991,9 @@ std::vector<IntVarLocalSearchFilter*> MakeCumulFilters(
|
||||
return {dimension.model()->solver()->RevAlloc(new ChainCumulFilter(
|
||||
*dimension.model(), dimension, objective_callback))};
|
||||
} else {
|
||||
filters.push_back(
|
||||
MakePathCumulFilter(dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value));
|
||||
filters.push_back(MakePathCumulFilter(
|
||||
dimension, objective_callback,
|
||||
propagate_path_cumul_filter_objective_value, filter_objective_cost));
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
@@ -2266,156 +2278,14 @@ IntVarLocalSearchFilter* MakeVehicleVarFilter(
|
||||
return routing_model.solver()->RevAlloc(new VehicleVarFilter(routing_model));
|
||||
}
|
||||
|
||||
namespace {
|
||||
class VehicleBreaksFilter : public BasePathFilter {
|
||||
public:
|
||||
VehicleBreaksFilter(const RoutingModel& routing_model,
|
||||
const RoutingDimension& dimension);
|
||||
std::string DebugString() const override { return "VehicleBreaksFilter"; }
|
||||
bool AcceptPath(int64 path_start, int64 chain_start,
|
||||
int64 chain_end) override;
|
||||
|
||||
private:
|
||||
// Handles to model.
|
||||
const RoutingModel& model_;
|
||||
const RoutingDimension& dimension_;
|
||||
// Strong energy-based filtering algorithm.
|
||||
DisjunctivePropagator disjunctive_propagator_;
|
||||
DisjunctivePropagator::Tasks tasks_;
|
||||
// Used to check whether propagation changed a vector.
|
||||
std::vector<int64> old_start_min_;
|
||||
std::vector<int64> old_start_max_;
|
||||
std::vector<int64> old_end_min_;
|
||||
std::vector<int64> old_end_max_;
|
||||
std::vector<int> start_to_vehicle_;
|
||||
};
|
||||
|
||||
VehicleBreaksFilter::VehicleBreaksFilter(const RoutingModel& routing_model,
|
||||
const RoutingDimension& dimension)
|
||||
: BasePathFilter(routing_model.Nexts(),
|
||||
routing_model.Size() + routing_model.vehicles(), nullptr),
|
||||
model_(routing_model),
|
||||
dimension_(dimension) {
|
||||
start_to_vehicle_.resize(Size(), -1);
|
||||
for (int i = 0; i < routing_model.vehicles(); ++i) {
|
||||
start_to_vehicle_[routing_model.Start(i)] = i;
|
||||
}
|
||||
}
|
||||
|
||||
bool VehicleBreaksFilter::AcceptPath(int64 path_start, int64 chain_start,
|
||||
int64 chain_end) {
|
||||
// Translate path and breaks to tasks.
|
||||
const int vehicle = start_to_vehicle_[path_start];
|
||||
if (!dimension_.VehicleHasBreakIntervals(vehicle)) return true;
|
||||
tasks_.start_min.clear();
|
||||
tasks_.start_max.clear();
|
||||
tasks_.duration_min.clear();
|
||||
tasks_.duration_max.clear();
|
||||
tasks_.end_min.clear();
|
||||
tasks_.end_max.clear();
|
||||
tasks_.is_preemptible.clear();
|
||||
tasks_.forbidden_intervals.clear();
|
||||
bool has_forbidden_intervals = false;
|
||||
int64 group_delay = 0LL;
|
||||
int64 current = path_start;
|
||||
while (true) {
|
||||
const int64 cumul_min = dimension_.CumulVar(current)->Min();
|
||||
const int64 cumul_max = dimension_.CumulVar(current)->Max();
|
||||
// Tasks from visits. Visits start before the group_delay at Cumul(current),
|
||||
// end at Cumul() + visit_duration.
|
||||
const bool node_is_last = model_.IsEnd(current);
|
||||
const int64 visit_duration =
|
||||
node_is_last
|
||||
? 0LL
|
||||
: dimension_.GetNodeVisitTransitsOfVehicle(vehicle)[current];
|
||||
tasks_.start_min.push_back(CapSub(cumul_min, group_delay));
|
||||
tasks_.start_max.push_back(CapSub(cumul_max, group_delay));
|
||||
tasks_.duration_min.push_back(CapAdd(group_delay, visit_duration));
|
||||
tasks_.duration_max.push_back(CapAdd(group_delay, visit_duration));
|
||||
tasks_.end_min.push_back(CapAdd(cumul_min, visit_duration));
|
||||
tasks_.end_max.push_back(CapAdd(cumul_max, visit_duration));
|
||||
tasks_.is_preemptible.push_back(false);
|
||||
tasks_.forbidden_intervals.push_back(
|
||||
&(dimension_.forbidden_intervals()[current]));
|
||||
has_forbidden_intervals |=
|
||||
tasks_.forbidden_intervals.back()->NumIntervals() > 0;
|
||||
if (node_is_last) break;
|
||||
|
||||
// Tasks from transits. Transit starts after the visit,
|
||||
// but before the next group_delay.
|
||||
const int next = GetNext(current);
|
||||
group_delay = dimension_.GetGroupDelay(vehicle, current, next);
|
||||
tasks_.start_min.push_back(CapAdd(cumul_min, visit_duration));
|
||||
tasks_.start_max.push_back(CapAdd(cumul_max, visit_duration));
|
||||
tasks_.duration_min.push_back(
|
||||
CapSub(CapSub(dimension_.transit_evaluator(vehicle)(current, next),
|
||||
visit_duration),
|
||||
group_delay));
|
||||
tasks_.duration_max.push_back(kint64max);
|
||||
tasks_.end_min.push_back(
|
||||
CapSub(dimension_.CumulVar(next)->Min(), group_delay));
|
||||
tasks_.end_max.push_back(
|
||||
CapSub(dimension_.CumulVar(next)->Max(), group_delay));
|
||||
tasks_.is_preemptible.push_back(true);
|
||||
tasks_.forbidden_intervals.push_back(nullptr);
|
||||
current = next;
|
||||
}
|
||||
tasks_.num_chain_tasks = tasks_.start_min.size();
|
||||
// Add tasks from breaks.
|
||||
for (IntervalVar* interval : dimension_.GetBreakIntervalsOfVehicle(vehicle)) {
|
||||
if (!interval->MustBePerformed()) continue;
|
||||
tasks_.start_min.push_back(interval->StartMin());
|
||||
tasks_.start_max.push_back(interval->StartMax());
|
||||
tasks_.duration_min.push_back(interval->DurationMin());
|
||||
tasks_.duration_max.push_back(interval->DurationMax());
|
||||
tasks_.end_min.push_back(interval->EndMin());
|
||||
tasks_.end_max.push_back(interval->EndMax());
|
||||
tasks_.is_preemptible.push_back(false);
|
||||
tasks_.forbidden_intervals.push_back(nullptr);
|
||||
}
|
||||
// Add side constraints.
|
||||
if (!has_forbidden_intervals) tasks_.forbidden_intervals.clear();
|
||||
tasks_.distance_duration.clear();
|
||||
if (dimension_.HasBreakDistanceDurationOfVehicle(vehicle)) {
|
||||
tasks_.distance_duration =
|
||||
dimension_.GetBreakDistanceDurationOfVehicle(vehicle);
|
||||
}
|
||||
// Reduce bounds until failure or fixed point is reached.
|
||||
// We set a maximum amount of iterations to avoid slow propagation.
|
||||
bool is_feasible = true;
|
||||
int maximum_num_iterations = 8;
|
||||
while (--maximum_num_iterations >= 0) {
|
||||
old_start_min_ = tasks_.start_min;
|
||||
old_start_max_ = tasks_.start_max;
|
||||
old_end_min_ = tasks_.end_min;
|
||||
old_end_max_ = tasks_.end_max;
|
||||
is_feasible = disjunctive_propagator_.Propagate(&tasks_);
|
||||
if (!is_feasible) break;
|
||||
// If fixed point reached, stop.
|
||||
if ((old_start_min_ == tasks_.start_min) &&
|
||||
(old_start_max_ == tasks_.start_max) &&
|
||||
(old_end_min_ == tasks_.end_min) && (old_end_max_ == tasks_.end_max)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return is_feasible;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IntVarLocalSearchFilter* MakeVehicleBreaksFilter(
|
||||
const RoutingModel& routing_model, const RoutingDimension& dimension) {
|
||||
return routing_model.solver()->RevAlloc(
|
||||
new VehicleBreaksFilter(routing_model, dimension));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class LPCumulFilter : public IntVarLocalSearchFilter {
|
||||
public:
|
||||
LPCumulFilter(const RoutingModel& routing_model,
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback);
|
||||
Solver::ObjectiveWatcher objective_callback,
|
||||
bool filter_objective_cost);
|
||||
bool Accept(Assignment* delta, Assignment* deltadelta) override;
|
||||
int64 GetAcceptedObjectiveValue() const override;
|
||||
void OnSynchronize(const Assignment* delta) override;
|
||||
@@ -2427,6 +2297,7 @@ class LPCumulFilter : public IntVarLocalSearchFilter {
|
||||
private:
|
||||
GlobalDimensionCumulOptimizer optimizer_;
|
||||
IntVar* const objective_;
|
||||
const bool filter_objective_cost_;
|
||||
int64 synchronized_cost_without_transit_;
|
||||
int64 delta_cost_without_transit_;
|
||||
SparseBitset<int64> delta_touched_;
|
||||
@@ -2435,11 +2306,13 @@ class LPCumulFilter : public IntVarLocalSearchFilter {
|
||||
|
||||
LPCumulFilter::LPCumulFilter(const RoutingModel& routing_model,
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback)
|
||||
Solver::ObjectiveWatcher objective_callback,
|
||||
bool filter_objective_cost)
|
||||
: IntVarLocalSearchFilter(routing_model.Nexts(),
|
||||
std::move(objective_callback)),
|
||||
optimizer_(&dimension),
|
||||
objective_(routing_model.CostVar()),
|
||||
filter_objective_cost_(filter_objective_cost),
|
||||
synchronized_cost_without_transit_(-1),
|
||||
delta_cost_without_transit_(-1),
|
||||
delta_touched_(Size()),
|
||||
@@ -2463,12 +2336,7 @@ bool LPCumulFilter::Accept(Assignment* delta, Assignment* deltadelta) {
|
||||
return delta_touched_[index] ? delta_nexts_[index] : Value(index);
|
||||
};
|
||||
|
||||
int64 objective_max = objective_->Max();
|
||||
if (delta->Objective() == objective_) {
|
||||
objective_max = std::min(objective_max, delta->ObjectiveMax());
|
||||
}
|
||||
|
||||
if (!CanPropagateObjectiveValue() && objective_max == kint64max) {
|
||||
if (!filter_objective_cost_) {
|
||||
// No need to compute the cost of the LP, only verify its feasibility.
|
||||
delta_cost_without_transit_ = -1;
|
||||
return optimizer_.IsFeasible(next_accessor);
|
||||
@@ -2489,6 +2357,11 @@ bool LPCumulFilter::Accept(Assignment* delta, Assignment* deltadelta) {
|
||||
}
|
||||
delta->SetObjectiveMin(new_objective_value);
|
||||
|
||||
int64 objective_max = objective_->Max();
|
||||
if (delta->Objective() == objective_) {
|
||||
objective_max = std::min(objective_max, delta->ObjectiveMax());
|
||||
}
|
||||
|
||||
return new_objective_value <= objective_max;
|
||||
}
|
||||
|
||||
@@ -2518,10 +2391,10 @@ int64 LPCumulFilter::GetSynchronizedObjectiveValue() const {
|
||||
|
||||
IntVarLocalSearchFilter* MakeGlobalLPCumulFilter(
|
||||
const RoutingDimension& dimension,
|
||||
Solver::ObjectiveWatcher objective_callback) {
|
||||
Solver::ObjectiveWatcher objective_callback, bool filter_objective_cost) {
|
||||
const RoutingModel* model = dimension.model();
|
||||
return model->solver()->RevAlloc(
|
||||
new LPCumulFilter(*model, dimension, std::move(objective_callback)));
|
||||
return model->solver()->RevAlloc(new LPCumulFilter(
|
||||
*model, dimension, std::move(objective_callback), filter_objective_cost));
|
||||
}
|
||||
|
||||
const int64 CPFeasibilityFilter::kUnassigned = -1;
|
||||
|
||||
Reference in New Issue
Block a user