3247 lines
152 KiB
C++
3247 lines
152 KiB
C++
// Copyright 2010-2022 Google LLC
|
|
// 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.
|
|
|
|
/// The vehicle routing library lets one model and solve generic vehicle routing
|
|
/// problems ranging from the Traveling Salesman Problem to more complex
|
|
/// problems such as the Capacitated Vehicle Routing Problem with Time Windows.
|
|
///
|
|
/// The objective of a vehicle routing problem is to build routes covering a set
|
|
/// of nodes minimizing the overall cost of the routes (usually proportional to
|
|
/// the sum of the lengths of each segment of the routes) while respecting some
|
|
/// problem-specific constraints (such as the length of a route). A route is
|
|
/// equivalent to a path connecting nodes, starting/ending at specific
|
|
/// starting/ending nodes.
|
|
///
|
|
/// The term "vehicle routing" is historical and the category of problems solved
|
|
/// is not limited to the routing of vehicles: any problem involving finding
|
|
/// routes visiting a given number of nodes optimally falls under this category
|
|
/// of problems, such as finding the optimal sequence in a playlist.
|
|
/// The literature around vehicle routing problems is extremely dense but one
|
|
/// can find some basic introductions in the following links:
|
|
/// - http://en.wikipedia.org/wiki/Travelling_salesman_problem
|
|
/// - http://www.tsp.gatech.edu/history/index.html
|
|
/// - http://en.wikipedia.org/wiki/Vehicle_routing_problem
|
|
///
|
|
/// The vehicle routing library is a vertical layer above the constraint
|
|
/// programming library (ortools/constraint_programming:cp).
|
|
/// One has access to all underlying constrained variables of the vehicle
|
|
/// routing model which can therefore be enriched by adding any constraint
|
|
/// available in the constraint programming library.
|
|
///
|
|
/// There are two sets of variables available:
|
|
/// - path variables:
|
|
/// * "next(i)" variables representing the immediate successor of the node
|
|
/// corresponding to i; use IndexToNode() to get the node corresponding to
|
|
/// a "next" variable value; note that node indices are strongly typed
|
|
/// integers (cf. ortools/base/int_type.h);
|
|
/// * "vehicle(i)" variables representing the vehicle route to which the
|
|
/// node corresponding to i belongs;
|
|
/// * "active(i)" boolean variables, true if the node corresponding to i is
|
|
/// visited and false if not; this can be false when nodes are either
|
|
/// optional or part of a disjunction;
|
|
/// * The following relationships hold for all i:
|
|
/// active(i) == 0 <=> next(i) == i <=> vehicle(i) == -1,
|
|
/// next(i) == j => vehicle(j) == vehicle(i).
|
|
/// - dimension variables, used when one is accumulating quantities along
|
|
/// routes, such as weight or volume carried, distance or time:
|
|
/// * "cumul(i,d)" variables representing the quantity of dimension d when
|
|
/// arriving at the node corresponding to i;
|
|
/// * "transit(i,d)" variables representing the quantity of dimension d added
|
|
/// after visiting the node corresponding to i.
|
|
/// * The following relationship holds for all (i,d):
|
|
/// next(i) == j => cumul(j,d) == cumul(i,d) + transit(i,d).
|
|
/// Solving the vehicle routing problems is mainly done using approximate
|
|
/// methods (namely local search,
|
|
/// cf. http://en.wikipedia.org/wiki/Local_search_(optimization) ), potentially
|
|
/// combined with exact techniques based on dynamic programming and exhaustive
|
|
/// tree search.
|
|
// TODO(user): Add a section on costs (vehicle arc costs, span costs,
|
|
// disjunctions costs).
|
|
//
|
|
/// Advanced tips: Flags are available to tune the search used to solve routing
|
|
/// problems. Here is a quick overview of the ones one might want to modify:
|
|
/// - Limiting the search for solutions:
|
|
/// * routing_solution_limit (default: kint64max): stop the search after
|
|
/// finding 'routing_solution_limit' improving solutions;
|
|
/// * routing_time_limit (default: kint64max): stop the search after
|
|
/// 'routing_time_limit' milliseconds;
|
|
/// - Customizing search:
|
|
/// * routing_first_solution (default: select the first node with an unbound
|
|
/// successor and connect it to the first available node): selects the
|
|
/// heuristic to build a first solution which will then be improved by local
|
|
/// search; possible values are GlobalCheapestArc (iteratively connect two
|
|
/// nodes which produce the cheapest route segment), LocalCheapestArc
|
|
/// (select the first node with an unbound successor and connect it to the
|
|
/// node which produces the cheapest route segment), PathCheapestArc
|
|
/// (starting from a route "start" node, connect it to the node which
|
|
/// produces the cheapest route segment, then extend the route by iterating
|
|
/// on the last node added to the route).
|
|
/// * Local search neighborhoods:
|
|
/// - routing_no_lns (default: false): forbids the use of Large Neighborhood
|
|
/// Search (LNS); LNS can find good solutions but is usually very slow.
|
|
/// Refer to the description of PATHLNS in the LocalSearchOperators enum
|
|
/// in constraint_solver.h for more information.
|
|
/// - routing_no_tsp (default: true): forbids the use of exact methods to
|
|
/// solve "sub"-traveling salesman problems (TSPs) of the current model
|
|
/// (such as sub-parts of a route, or one route in a multiple route
|
|
/// problem). Uses dynamic programming to solve such TSPs with a maximum
|
|
/// size (in number of nodes) up to cp_local_search_tsp_opt_size (flag
|
|
/// with a default value of 13 nodes). It is not activated by default
|
|
/// because it can slow down the search.
|
|
/// * Meta-heuristics: used to guide the search out of local minima found by
|
|
/// local search. Note that, in general, a search with metaheuristics
|
|
/// activated never stops, therefore one must specify a search limit.
|
|
/// Several types of metaheuristics are provided:
|
|
/// - routing_guided_local_search (default: false): activates guided local
|
|
/// search (cf. http://en.wikipedia.org/wiki/Guided_Local_Search);
|
|
/// this is generally the most efficient metaheuristic for vehicle
|
|
/// routing;
|
|
/// - routing_simulated_annealing (default: false): activates simulated
|
|
/// annealing (cf. http://en.wikipedia.org/wiki/Simulated_annealing);
|
|
/// - routing_tabu_search (default: false): activates tabu search (cf.
|
|
/// http://en.wikipedia.org/wiki/Tabu_search).
|
|
///
|
|
/// Code sample:
|
|
/// Here is a simple example solving a traveling salesman problem given a cost
|
|
/// function callback (returns the cost of a route segment):
|
|
///
|
|
/// - Define a custom distance/cost function from an index to another; in this
|
|
/// example just returns the sum of the indices:
|
|
///
|
|
/// int64_t MyDistance(int64_t from, int64_t to) {
|
|
/// return from + to;
|
|
/// }
|
|
///
|
|
/// - Create a routing model for a given problem size (int number of nodes) and
|
|
/// number of routes (here, 1):
|
|
///
|
|
/// RoutingIndexManager manager(...number of nodes..., 1);
|
|
/// RoutingModel routing(manager);
|
|
///
|
|
/// - Set the cost function by registering an std::function<int64_t(int64_t,
|
|
/// int64_t)> in the model and passing its index as the vehicle cost.
|
|
///
|
|
/// const int cost = routing.RegisterTransitCallback(MyDistance);
|
|
/// routing.SetArcCostEvaluatorOfAllVehicles(cost);
|
|
///
|
|
/// - Find a solution using Solve(), returns a solution if any (owned by
|
|
/// routing):
|
|
///
|
|
/// const Assignment* solution = routing.Solve();
|
|
/// CHECK(solution != nullptr);
|
|
///
|
|
/// - Inspect the solution cost and route (only one route here):
|
|
///
|
|
/// LOG(INFO) << "Cost " << solution->ObjectiveValue();
|
|
/// const int route_number = 0;
|
|
/// for (int64_t node = routing.Start(route_number);
|
|
/// !routing.IsEnd(node);
|
|
/// node = solution->Value(routing.NextVar(node))) {
|
|
/// LOG(INFO) << manager.IndexToNode(node);
|
|
/// }
|
|
///
|
|
///
|
|
/// Keywords: Vehicle Routing, Traveling Salesman Problem, TSP, VRP, CVRPTW,
|
|
/// PDP.
|
|
|
|
#ifndef OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_H_
|
|
#define OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_H_
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/container/flat_hash_map.h"
|
|
#include "absl/container/flat_hash_set.h"
|
|
#include "absl/container/inlined_vector.h"
|
|
#include "absl/time/time.h"
|
|
#include "ortools/base/int_type.h"
|
|
#include "ortools/base/integral_types.h"
|
|
#include "ortools/base/logging.h"
|
|
#include "ortools/base/macros.h"
|
|
#include "ortools/base/strong_vector.h"
|
|
#include "ortools/constraint_solver/constraint_solver.h"
|
|
#include "ortools/constraint_solver/constraint_solveri.h"
|
|
#include "ortools/constraint_solver/routing_enums.pb.h"
|
|
#include "ortools/constraint_solver/routing_index_manager.h"
|
|
#include "ortools/constraint_solver/routing_parameters.pb.h"
|
|
#include "ortools/constraint_solver/routing_types.h"
|
|
#include "ortools/graph/graph.h"
|
|
#include "ortools/sat/theta_tree.h"
|
|
#include "ortools/util/piecewise_linear_function.h"
|
|
#include "ortools/util/range_query_function.h"
|
|
#include "ortools/util/saturated_arithmetic.h"
|
|
#include "ortools/util/sorted_interval_list.h"
|
|
|
|
namespace operations_research {
|
|
|
|
class GlobalDimensionCumulOptimizer;
|
|
class LocalDimensionCumulOptimizer;
|
|
class LocalSearchPhaseParameters;
|
|
#ifndef SWIG
|
|
class IndexNeighborFinder;
|
|
class IntVarFilteredDecisionBuilder;
|
|
#endif
|
|
class RoutingDimension;
|
|
#ifndef SWIG
|
|
using util::ReverseArcListGraph;
|
|
class SweepArranger;
|
|
#endif
|
|
|
|
class PathsMetadata {
|
|
public:
|
|
explicit PathsMetadata(const RoutingIndexManager& manager) {
|
|
const int num_indices = manager.num_indices();
|
|
const int num_paths = manager.num_vehicles();
|
|
path_of_node_.resize(num_indices, -1);
|
|
is_start_.resize(num_indices, false);
|
|
is_end_.resize(num_indices, false);
|
|
start_of_path_.resize(num_paths);
|
|
end_of_path_.resize(num_paths);
|
|
for (int v = 0; v < num_paths; ++v) {
|
|
const int64_t start = manager.GetStartIndex(v);
|
|
start_of_path_[v] = start;
|
|
path_of_node_[start] = v;
|
|
is_start_[start] = true;
|
|
const int64_t end = manager.GetEndIndex(v);
|
|
end_of_path_[v] = end;
|
|
path_of_node_[end] = v;
|
|
is_end_[end] = true;
|
|
}
|
|
}
|
|
|
|
bool IsStart(int64_t node) const { return is_start_[node]; }
|
|
bool IsEnd(int64_t node) const { return is_end_[node]; }
|
|
int GetPath(int64_t start_or_end_node) const {
|
|
return path_of_node_[start_or_end_node];
|
|
}
|
|
const std::vector<int64_t>& Starts() const { return start_of_path_; }
|
|
const std::vector<int64_t>& Ends() const { return end_of_path_; }
|
|
|
|
private:
|
|
std::vector<bool> is_start_;
|
|
std::vector<bool> is_end_;
|
|
std::vector<int64_t> start_of_path_;
|
|
std::vector<int64_t> end_of_path_;
|
|
std::vector<int64_t> path_of_node_;
|
|
};
|
|
|
|
class RoutingModel {
|
|
public:
|
|
/// Status of the search.
|
|
enum Status {
|
|
/// Problem not solved yet (before calling RoutingModel::Solve()).
|
|
ROUTING_NOT_SOLVED,
|
|
/// Problem solved successfully after calling RoutingModel::Solve().
|
|
ROUTING_SUCCESS,
|
|
/// No solution found to the problem after calling RoutingModel::Solve().
|
|
ROUTING_FAIL,
|
|
/// Time limit reached before finding a solution with RoutingModel::Solve().
|
|
ROUTING_FAIL_TIMEOUT,
|
|
/// Model, model parameters or flags are not valid.
|
|
ROUTING_INVALID,
|
|
/// Problem proven to be infeasible.
|
|
ROUTING_INFEASIBLE
|
|
};
|
|
|
|
/// Types of precedence policy applied to pickup and delivery pairs.
|
|
enum PickupAndDeliveryPolicy {
|
|
/// Any precedence is accepted.
|
|
PICKUP_AND_DELIVERY_NO_ORDER,
|
|
/// Deliveries must be performed in reverse order of pickups.
|
|
PICKUP_AND_DELIVERY_LIFO,
|
|
/// Deliveries must be performed in the same order as pickups.
|
|
PICKUP_AND_DELIVERY_FIFO
|
|
};
|
|
typedef RoutingCostClassIndex CostClassIndex;
|
|
typedef RoutingDimensionIndex DimensionIndex;
|
|
typedef RoutingDisjunctionIndex DisjunctionIndex;
|
|
typedef RoutingVehicleClassIndex VehicleClassIndex;
|
|
typedef RoutingTransitCallback1 TransitCallback1;
|
|
typedef RoutingTransitCallback2 TransitCallback2;
|
|
|
|
// TODO(user): Remove all SWIG guards by adding the @ignore in .i.
|
|
#if !defined(SWIG)
|
|
typedef RoutingIndexPair IndexPair;
|
|
typedef RoutingIndexPairs IndexPairs;
|
|
#endif // SWIG
|
|
|
|
#if !defined(SWIG)
|
|
/// What follows is relevant for models with time/state dependent transits.
|
|
/// Such transits, say from node A to node B, are functions f:
|
|
/// int64_t->int64_t of the cumuls of a dimension. The user is free to
|
|
/// implement the abstract RangeIntToIntFunction interface, but it is expected
|
|
/// that the implementation of each method is quite fast. For
|
|
/// performance-related reasons, StateDependentTransit keeps an additional
|
|
/// pointer to a RangeMinMaxIndexFunction, with similar functionality to
|
|
/// RangeIntToIntFunction, for g(x) = f(x)+x, where f is the transit from A to
|
|
/// B. In most situations the best solutions are problem-specific, but in case
|
|
/// of doubt the user may use the MakeStateDependentTransit function from the
|
|
/// routing library, which works out-of-the-box, with very good running time,
|
|
/// but memory inefficient in some situations.
|
|
struct StateDependentTransit {
|
|
RangeIntToIntFunction* transit; /// f(x)
|
|
RangeMinMaxIndexFunction* transit_plus_identity; /// g(x) = f(x) + x
|
|
};
|
|
typedef std::function<StateDependentTransit(int64_t, int64_t)>
|
|
VariableIndexEvaluator2;
|
|
#endif // SWIG
|
|
|
|
#if !defined(SWIG)
|
|
struct CostClass {
|
|
/// Index of the arc cost evaluator, registered in the RoutingModel class.
|
|
int evaluator_index = 0;
|
|
|
|
/// SUBTLE:
|
|
/// The vehicle's fixed cost is skipped on purpose here, because we
|
|
/// can afford to do so:
|
|
/// - We don't really care about creating "strict" equivalence classes;
|
|
/// all we care about is to:
|
|
/// 1) compress the space of cost callbacks so that
|
|
/// we can cache them more efficiently.
|
|
/// 2) have a smaller IntVar domain thanks to using a "cost class var"
|
|
/// instead of the vehicle var, so that we reduce the search space.
|
|
/// Both of these are an incentive for *fewer* cost classes. Ignoring
|
|
/// the fixed costs can only be good in that regard.
|
|
/// - The fixed costs are only needed when evaluating the cost of the
|
|
/// first arc of the route, in which case we know the vehicle, since we
|
|
/// have the route's start node.
|
|
|
|
/// Only dimensions that have non-zero cost evaluator and a non-zero cost
|
|
/// coefficient (in this cost class) are listed here. Since we only need
|
|
/// their transit evaluator (the raw version that takes var index, not Node
|
|
/// Index) and their span cost coefficient, we just store those.
|
|
/// This is sorted by the natural operator < (and *not* by DimensionIndex).
|
|
struct DimensionCost {
|
|
int64_t transit_evaluator_class;
|
|
int64_t cost_coefficient;
|
|
const RoutingDimension* dimension;
|
|
bool operator<(const DimensionCost& cost) const {
|
|
if (transit_evaluator_class != cost.transit_evaluator_class) {
|
|
return transit_evaluator_class < cost.transit_evaluator_class;
|
|
}
|
|
return cost_coefficient < cost.cost_coefficient;
|
|
}
|
|
};
|
|
std::vector<DimensionCost>
|
|
dimension_transit_evaluator_class_and_cost_coefficient;
|
|
|
|
explicit CostClass(int evaluator_index)
|
|
: evaluator_index(evaluator_index) {}
|
|
|
|
/// Comparator for STL containers and algorithms.
|
|
static bool LessThan(const CostClass& a, const CostClass& b) {
|
|
if (a.evaluator_index != b.evaluator_index) {
|
|
return a.evaluator_index < b.evaluator_index;
|
|
}
|
|
return a.dimension_transit_evaluator_class_and_cost_coefficient <
|
|
b.dimension_transit_evaluator_class_and_cost_coefficient;
|
|
}
|
|
};
|
|
|
|
struct VehicleClass {
|
|
/// The cost class of the vehicle.
|
|
CostClassIndex cost_class_index;
|
|
/// Contrarily to CostClass, here we need strict equivalence.
|
|
int64_t fixed_cost;
|
|
/// Whether or not the vehicle is used when empty.
|
|
bool used_when_empty;
|
|
/// Vehicle start and end equivalence classes. Currently if two vehicles
|
|
/// have different start/end nodes which are "physically" located at the
|
|
/// same place, these two vehicles will be considered as non-equivalent
|
|
/// unless the two indices are in the same class.
|
|
// TODO(user): Find equivalent start/end nodes wrt dimensions and
|
|
// callbacks.
|
|
int start_equivalence_class;
|
|
int end_equivalence_class;
|
|
/// Bounds of cumul variables at start and end vehicle nodes.
|
|
/// dimension_{start,end}_cumuls_{min,max}[d] is the bound for dimension d.
|
|
absl::StrongVector<DimensionIndex, int64_t> dimension_start_cumuls_min;
|
|
absl::StrongVector<DimensionIndex, int64_t> dimension_start_cumuls_max;
|
|
absl::StrongVector<DimensionIndex, int64_t> dimension_end_cumuls_min;
|
|
absl::StrongVector<DimensionIndex, int64_t> dimension_end_cumuls_max;
|
|
absl::StrongVector<DimensionIndex, int64_t> dimension_capacities;
|
|
/// dimension_evaluators[d]->Run(from, to) is the transit value of arc
|
|
/// from->to for a dimension d.
|
|
absl::StrongVector<DimensionIndex, int64_t> dimension_evaluator_classes;
|
|
/// Fingerprint of unvisitable non-start/end nodes.
|
|
uint64_t unvisitable_nodes_fprint;
|
|
/// Sorted set of resource groups for which the vehicle requires a resource.
|
|
std::vector<int> required_resource_group_indices;
|
|
|
|
/// Comparator for STL containers and algorithms.
|
|
static bool LessThan(const VehicleClass& a, const VehicleClass& b);
|
|
};
|
|
#endif // defined(SWIG)
|
|
|
|
/// Struct used to sort and store vehicles by their type. Two vehicles have
|
|
/// the same "vehicle type" iff they have the same cost class and start/end
|
|
/// nodes.
|
|
struct VehicleTypeContainer {
|
|
struct VehicleClassEntry {
|
|
int vehicle_class;
|
|
int64_t fixed_cost;
|
|
|
|
bool operator<(const VehicleClassEntry& other) const {
|
|
return std::tie(fixed_cost, vehicle_class) <
|
|
std::tie(other.fixed_cost, other.vehicle_class);
|
|
}
|
|
};
|
|
|
|
int NumTypes() const { return sorted_vehicle_classes_per_type.size(); }
|
|
|
|
int Type(int vehicle) const {
|
|
DCHECK_LT(vehicle, type_index_of_vehicle.size());
|
|
return type_index_of_vehicle[vehicle];
|
|
}
|
|
|
|
std::vector<int> type_index_of_vehicle;
|
|
// clang-format off
|
|
std::vector<std::set<VehicleClassEntry> > sorted_vehicle_classes_per_type;
|
|
std::vector<std::deque<int> > vehicles_per_vehicle_class;
|
|
// clang-format on
|
|
};
|
|
|
|
/// A ResourceGroup defines a set of available Resources with attributes on
|
|
/// one or multiple dimensions.
|
|
/// For every ResourceGroup in the model, each (used) vehicle in the solution
|
|
/// which requires a resource (see NotifyVehicleRequiresResource()) from this
|
|
/// group must be assigned to exactly 1 resource, and each resource can in
|
|
/// turn be assigned to at most 1 vehicle requiring it. This
|
|
/// vehicle-to-resource assignment will apply the corresponding Attributes to
|
|
/// the dimensions affected by the resource group. NOTE: As of 2021/07, each
|
|
/// ResourceGroup can only affect a single RoutingDimension at a time, i.e.
|
|
/// all Resources in a group must apply attributes to the same single
|
|
/// dimension.
|
|
class ResourceGroup {
|
|
public:
|
|
/// Attributes for a dimension.
|
|
class Attributes {
|
|
public:
|
|
Attributes();
|
|
Attributes(Domain start_domain, Domain end_domain);
|
|
|
|
const Domain& start_domain() const { return start_domain_; }
|
|
const Domain& end_domain() const { return end_domain_; }
|
|
|
|
private:
|
|
/// The following domains constrain the dimension start/end cumul of the
|
|
/// the vehicle assigned to this resource:
|
|
/// start_domain_.Min() <= cumul[Start(v)] <= start_domain_.Max()
|
|
Domain start_domain_;
|
|
/// end_domain_.Min() <= cumul[End(v)] <= end_domain_.Max()
|
|
Domain end_domain_;
|
|
};
|
|
|
|
/// A Resource sets attributes (costs/constraints) for a set of dimensions.
|
|
class Resource {
|
|
public:
|
|
const ResourceGroup::Attributes& GetDimensionAttributes(
|
|
const RoutingDimension* dimension) const;
|
|
|
|
private:
|
|
explicit Resource(const RoutingModel* model) : model_(model) {}
|
|
|
|
void SetDimensionAttributes(ResourceGroup::Attributes attributes,
|
|
const RoutingDimension* dimension);
|
|
const ResourceGroup::Attributes& GetDefaultAttributes() const;
|
|
|
|
const RoutingModel* const model_;
|
|
absl::flat_hash_map<DimensionIndex, ResourceGroup::Attributes>
|
|
dimension_attributes_;
|
|
|
|
friend class ResourceGroup;
|
|
};
|
|
|
|
explicit ResourceGroup(const RoutingModel* model)
|
|
: model_(model), vehicle_requires_resource_(model->vehicles(), false) {}
|
|
|
|
/// Adds a Resource with the given attributes for the corresponding
|
|
/// dimension. Returns the index of the added resource in resources_.
|
|
int AddResource(Attributes attributes, const RoutingDimension* dimension);
|
|
|
|
/// Notifies that the given vehicle index requires a resource from this
|
|
/// group if the vehicle is used (i.e. if its route is non-empty or
|
|
/// vehicle_used_when_empty_[vehicle] is true).
|
|
void NotifyVehicleRequiresAResource(int vehicle);
|
|
|
|
const std::vector<int>& GetVehiclesRequiringAResource() const {
|
|
return vehicles_requiring_resource_;
|
|
}
|
|
|
|
bool VehicleRequiresAResource(int vehicle) const {
|
|
return vehicle_requires_resource_[vehicle];
|
|
}
|
|
|
|
const std::vector<Resource>& GetResources() const { return resources_; }
|
|
const Resource& GetResource(int resource_index) const {
|
|
DCHECK_LT(resource_index, resources_.size());
|
|
return resources_[resource_index];
|
|
}
|
|
const absl::flat_hash_set<DimensionIndex>& GetAffectedDimensionIndices()
|
|
const {
|
|
return affected_dimension_indices_;
|
|
}
|
|
int Size() const { return resources_.size(); }
|
|
|
|
private:
|
|
const RoutingModel* const model_;
|
|
std::vector<Resource> resources_;
|
|
std::vector<bool> vehicle_requires_resource_;
|
|
std::vector<int> vehicles_requiring_resource_;
|
|
/// All indices of dimensions affected by this resource group.
|
|
absl::flat_hash_set<DimensionIndex> affected_dimension_indices_;
|
|
};
|
|
|
|
/// Constant used to express a hard constraint instead of a soft penalty.
|
|
static const int64_t kNoPenalty;
|
|
|
|
/// Constant used to express the "no disjunction" index, returned when a node
|
|
/// does not appear in any disjunction.
|
|
static const DisjunctionIndex kNoDisjunction;
|
|
|
|
/// Constant used to express the "no dimension" index, returned when a
|
|
/// dimension name does not correspond to an actual dimension.
|
|
static const DimensionIndex kNoDimension;
|
|
|
|
/// Constructor taking an index manager. The version which does not take
|
|
/// RoutingModelParameters is equivalent to passing
|
|
/// DefaultRoutingModelParameters().
|
|
explicit RoutingModel(const RoutingIndexManager& index_manager);
|
|
RoutingModel(const RoutingIndexManager& index_manager,
|
|
const RoutingModelParameters& parameters);
|
|
~RoutingModel();
|
|
|
|
/// Registers 'callback' and returns its index.
|
|
int RegisterUnaryTransitVector(std::vector<int64_t> values);
|
|
int RegisterUnaryTransitCallback(TransitCallback1 callback);
|
|
int RegisterPositiveUnaryTransitCallback(TransitCallback1 callback);
|
|
|
|
int RegisterTransitMatrix(
|
|
std::vector<std::vector<int64_t> /*needed_for_swig*/> values);
|
|
int RegisterTransitCallback(TransitCallback2 callback);
|
|
int RegisterPositiveTransitCallback(TransitCallback2 callback);
|
|
|
|
int RegisterStateDependentTransitCallback(VariableIndexEvaluator2 callback);
|
|
const TransitCallback2& TransitCallback(int callback_index) const {
|
|
CHECK_LT(callback_index, transit_evaluators_.size());
|
|
return transit_evaluators_[callback_index];
|
|
}
|
|
const TransitCallback1& UnaryTransitCallbackOrNull(int callback_index) const {
|
|
CHECK_LT(callback_index, unary_transit_evaluators_.size());
|
|
return unary_transit_evaluators_[callback_index];
|
|
}
|
|
const VariableIndexEvaluator2& StateDependentTransitCallback(
|
|
int callback_index) const {
|
|
CHECK_LT(callback_index, state_dependent_transit_evaluators_.size());
|
|
return state_dependent_transit_evaluators_[callback_index];
|
|
}
|
|
|
|
/// Model creation
|
|
|
|
/// Methods to add dimensions to routes; dimensions represent quantities
|
|
/// accumulated at nodes along the routes. They represent quantities such as
|
|
/// weights or volumes carried along the route, or distance or times.
|
|
/// Quantities at a node are represented by "cumul" variables and the increase
|
|
/// or decrease of quantities between nodes are represented by "transit"
|
|
/// variables. These variables are linked as follows:
|
|
/// if j == next(i), cumul(j) = cumul(i) + transit(i) + slack(i)
|
|
/// where slack is a positive slack variable (can represent waiting times for
|
|
/// a time dimension).
|
|
/// Setting the value of fix_start_cumul_to_zero to true will force the
|
|
/// "cumul" variable of the start node of all vehicles to be equal to 0.
|
|
|
|
/// Creates a dimension where the transit variable is constrained to be
|
|
/// equal to evaluator(i, next(i)); 'slack_max' is the upper bound of the
|
|
/// slack variable and 'capacity' is the upper bound of the cumul variables.
|
|
/// 'name' is the name used to reference the dimension; this name is used to
|
|
/// get cumul and transit variables from the routing model.
|
|
/// Returns false if a dimension with the same name has already been created
|
|
/// (and doesn't create the new dimension).
|
|
/// Takes ownership of the callback 'evaluator'.
|
|
bool AddDimension(int evaluator_index, int64_t slack_max, int64_t capacity,
|
|
bool fix_start_cumul_to_zero, const std::string& name);
|
|
bool AddDimensionWithVehicleTransits(
|
|
const std::vector<int>& evaluator_indices, int64_t slack_max,
|
|
int64_t capacity, bool fix_start_cumul_to_zero, const std::string& name);
|
|
bool AddDimensionWithVehicleCapacity(int evaluator_index, int64_t slack_max,
|
|
std::vector<int64_t> vehicle_capacities,
|
|
bool fix_start_cumul_to_zero,
|
|
const std::string& name);
|
|
bool AddDimensionWithVehicleTransitAndCapacity(
|
|
const std::vector<int>& evaluator_indices, int64_t slack_max,
|
|
std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
|
|
const std::string& name);
|
|
/// Creates a dimension where the transit variable is constrained to be
|
|
/// equal to 'value'; 'capacity' is the upper bound of the cumul variables.
|
|
/// 'name' is the name used to reference the dimension; this name is used to
|
|
/// get cumul and transit variables from the routing model.
|
|
/// Returns a pair consisting of an index to the registered unary transit
|
|
/// callback and a bool denoting whether the dimension has been created.
|
|
/// It is false if a dimension with the same name has already been created
|
|
/// (and doesn't create the new dimension but still register a new callback).
|
|
std::pair<int, bool> AddConstantDimensionWithSlack(
|
|
int64_t value, int64_t capacity, int64_t slack_max,
|
|
bool fix_start_cumul_to_zero, const std::string& name);
|
|
std::pair<int, bool> AddConstantDimension(int64_t value, int64_t capacity,
|
|
bool fix_start_cumul_to_zero,
|
|
const std::string& name) {
|
|
return AddConstantDimensionWithSlack(value, capacity, 0,
|
|
fix_start_cumul_to_zero, name);
|
|
}
|
|
/// Creates a dimension where the transit variable is constrained to be
|
|
/// equal to 'values[i]' for node i; 'capacity' is the upper bound of
|
|
/// the cumul variables. 'name' is the name used to reference the dimension;
|
|
/// this name is used to get cumul and transit variables from the routing
|
|
/// model.
|
|
/// Returns a pair consisting of an index to the registered unary transit
|
|
/// callback and a bool denoting whether the dimension has been created.
|
|
/// It is false if a dimension with the same name has already been created
|
|
/// (and doesn't create the new dimension but still register a new callback).
|
|
std::pair<int, bool> AddVectorDimension(std::vector<int64_t> values,
|
|
int64_t capacity,
|
|
bool fix_start_cumul_to_zero,
|
|
const std::string& name);
|
|
/// Creates a dimension where the transit variable is constrained to be
|
|
/// equal to 'values[i][next(i)]' for node i; 'capacity' is the upper bound of
|
|
/// the cumul variables. 'name' is the name used to reference the dimension;
|
|
/// this name is used to get cumul and transit variables from the routing
|
|
/// model.
|
|
/// Returns a pair consisting of an index to the registered transit callback
|
|
/// and a bool denoting whether the dimension has been created.
|
|
/// It is false if a dimension with the same name has already been created
|
|
/// (and doesn't create the new dimension but still register a new callback).
|
|
std::pair<int, bool> AddMatrixDimension(
|
|
std::vector<std::vector<int64_t> /*needed_for_swig*/> values,
|
|
int64_t capacity, bool fix_start_cumul_to_zero, const std::string& name);
|
|
/// Creates a dimension with transits depending on the cumuls of another
|
|
/// dimension. 'pure_transits' are the per-vehicle fixed transits as above.
|
|
/// 'dependent_transits' is a vector containing for each vehicle an index to a
|
|
/// registered state dependent transit callback. 'base_dimension' indicates
|
|
/// the dimension from which the cumul variable is taken. If 'base_dimension'
|
|
/// is nullptr, then the newly created dimension is self-based.
|
|
bool AddDimensionDependentDimensionWithVehicleCapacity(
|
|
const std::vector<int>& pure_transits,
|
|
const std::vector<int>& dependent_transits,
|
|
const RoutingDimension* base_dimension, int64_t slack_max,
|
|
std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
|
|
const std::string& name) {
|
|
return AddDimensionDependentDimensionWithVehicleCapacityInternal(
|
|
pure_transits, dependent_transits, base_dimension, slack_max,
|
|
std::move(vehicle_capacities), fix_start_cumul_to_zero, name);
|
|
}
|
|
|
|
/// As above, but pure_transits are taken to be zero evaluators.
|
|
bool AddDimensionDependentDimensionWithVehicleCapacity(
|
|
const std::vector<int>& transits, const RoutingDimension* base_dimension,
|
|
int64_t slack_max, std::vector<int64_t> vehicle_capacities,
|
|
bool fix_start_cumul_to_zero, const std::string& name);
|
|
/// Homogeneous versions of the functions above.
|
|
bool AddDimensionDependentDimensionWithVehicleCapacity(
|
|
int transit, const RoutingDimension* base_dimension, int64_t slack_max,
|
|
int64_t vehicle_capacity, bool fix_start_cumul_to_zero,
|
|
const std::string& name);
|
|
bool AddDimensionDependentDimensionWithVehicleCapacity(
|
|
int pure_transit, int dependent_transit,
|
|
const RoutingDimension* base_dimension, int64_t slack_max,
|
|
int64_t vehicle_capacity, bool fix_start_cumul_to_zero,
|
|
const std::string& name);
|
|
|
|
/// Creates a cached StateDependentTransit from an std::function.
|
|
static RoutingModel::StateDependentTransit MakeStateDependentTransit(
|
|
const std::function<int64_t(int64_t)>& f, int64_t domain_start,
|
|
int64_t 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).
|
|
/// - 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* 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.
|
|
std::vector<std::string> GetAllDimensionNames() const;
|
|
/// Returns all dimensions of the model.
|
|
const std::vector<RoutingDimension*>& GetDimensions() const {
|
|
return dimensions_.get();
|
|
}
|
|
/// Returns dimensions with soft or vehicle span costs.
|
|
std::vector<RoutingDimension*> GetDimensionsWithSoftOrSpanCosts() const;
|
|
|
|
/// Returns the dimensions which have [global|local]_dimension_optimizers_.
|
|
std::vector<const RoutingDimension*> GetDimensionsWithGlobalCumulOptimizers()
|
|
const;
|
|
std::vector<const RoutingDimension*> GetDimensionsWithLocalCumulOptimizers()
|
|
const;
|
|
|
|
/// Returns whether the given dimension has global/local cumul optimizers.
|
|
bool HasGlobalCumulOptimizer(const RoutingDimension& dimension) const {
|
|
return GetGlobalCumulOptimizerIndex(dimension) >= 0;
|
|
}
|
|
bool HasLocalCumulOptimizer(const RoutingDimension& dimension) const {
|
|
return GetLocalCumulOptimizerIndex(dimension) >= 0;
|
|
}
|
|
/// Returns the global/local dimension cumul optimizer for a given dimension,
|
|
/// or nullptr if there is none.
|
|
GlobalDimensionCumulOptimizer* GetMutableGlobalCumulLPOptimizer(
|
|
const RoutingDimension& dimension) const;
|
|
GlobalDimensionCumulOptimizer* GetMutableGlobalCumulMPOptimizer(
|
|
const RoutingDimension& dimension) const;
|
|
LocalDimensionCumulOptimizer* GetMutableLocalCumulLPOptimizer(
|
|
const RoutingDimension& dimension) const;
|
|
LocalDimensionCumulOptimizer* GetMutableLocalCumulMPOptimizer(
|
|
const RoutingDimension& dimension) const;
|
|
|
|
/// Returns true if a dimension exists for a given dimension name.
|
|
bool HasDimension(const std::string& dimension_name) const;
|
|
/// Returns a dimension from its name. Dies if the dimension does not exist.
|
|
const RoutingDimension& GetDimensionOrDie(
|
|
const std::string& dimension_name) const;
|
|
/// Returns a dimension from its name. Returns nullptr if the dimension does
|
|
/// not exist.
|
|
RoutingDimension* GetMutableDimension(
|
|
const std::string& dimension_name) const;
|
|
/// Set the given dimension as "primary constrained". As of August 2013, this
|
|
/// is only used by ArcIsMoreConstrainedThanArc().
|
|
/// "dimension" must be the name of an existing dimension, or be empty, in
|
|
/// which case there will not be a primary dimension after this call.
|
|
void SetPrimaryConstrainedDimension(const std::string& dimension_name) {
|
|
DCHECK(dimension_name.empty() || HasDimension(dimension_name));
|
|
primary_constrained_dimension_ = dimension_name;
|
|
}
|
|
/// Get the primary constrained dimension, or an empty string if it is unset.
|
|
const std::string& GetPrimaryConstrainedDimension() const {
|
|
return primary_constrained_dimension_;
|
|
}
|
|
|
|
/// Adds a resource group to the routing model. Returns its index in
|
|
/// resource_groups_.
|
|
int AddResourceGroup();
|
|
// clang-format off
|
|
const std::vector<std::unique_ptr<ResourceGroup> >& GetResourceGroups()
|
|
const {
|
|
return resource_groups_;
|
|
}
|
|
// clang-format on
|
|
ResourceGroup* GetResourceGroup(int rg_index) const {
|
|
DCHECK_LT(rg_index, resource_groups_.size());
|
|
return resource_groups_[rg_index].get();
|
|
}
|
|
|
|
/// Returns the indices of resource groups for this dimension. This method can
|
|
/// only be called after the model has been closed.
|
|
const std::vector<int>& GetDimensionResourceGroupIndices(
|
|
const RoutingDimension* dimension) const;
|
|
|
|
/// Returns the index of the resource group attached to the dimension.
|
|
/// DCHECKS that there's exactly one resource group for this dimension.
|
|
int GetDimensionResourceGroupIndex(const RoutingDimension* dimension) const {
|
|
DCHECK_EQ(GetDimensionResourceGroupIndices(dimension).size(), 1);
|
|
return GetDimensionResourceGroupIndices(dimension)[0];
|
|
}
|
|
|
|
/// Adds a disjunction constraint on the indices: exactly 'max_cardinality' of
|
|
/// the indices are active. Start and end indices of any vehicle cannot be
|
|
/// part of a disjunction.
|
|
///
|
|
/// If a penalty is given, at most 'max_cardinality' of the indices can be
|
|
/// active, and if less are active, 'penalty' is payed per inactive index.
|
|
/// This is equivalent to adding the constraint:
|
|
/// p + Sum(i)active[i] == max_cardinality
|
|
/// where p is an integer variable, and the following cost to the cost
|
|
/// function:
|
|
/// p * penalty.
|
|
/// 'penalty' must be positive to make the disjunction optional; a negative
|
|
/// penalty will force 'max_cardinality' indices of the disjunction to be
|
|
/// performed, and therefore p == 0.
|
|
/// Note: passing a vector with a single index will model an optional index
|
|
/// with a penalty cost if it is not visited.
|
|
DisjunctionIndex AddDisjunction(const std::vector<int64_t>& indices,
|
|
int64_t penalty = kNoPenalty,
|
|
int64_t max_cardinality = 1);
|
|
/// Returns the indices of the disjunctions to which an index belongs.
|
|
const std::vector<DisjunctionIndex>& GetDisjunctionIndices(
|
|
int64_t index) const {
|
|
return index_to_disjunctions_[index];
|
|
}
|
|
/// Calls f for each variable index of indices in the same disjunctions as the
|
|
/// node corresponding to the variable index 'index'; only disjunctions of
|
|
/// cardinality 'cardinality' are considered.
|
|
template <typename F>
|
|
void ForEachNodeInDisjunctionWithMaxCardinalityFromIndex(
|
|
int64_t index, int64_t max_cardinality, F f) const {
|
|
for (const DisjunctionIndex disjunction : GetDisjunctionIndices(index)) {
|
|
if (disjunctions_[disjunction].value.max_cardinality == max_cardinality) {
|
|
for (const int64_t d_index : disjunctions_[disjunction].indices) {
|
|
f(d_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if !defined(SWIGPYTHON)
|
|
/// Returns the variable indices of the nodes in the disjunction of index
|
|
/// 'index'.
|
|
const std::vector<int64_t>& GetDisjunctionNodeIndices(
|
|
DisjunctionIndex index) const {
|
|
return disjunctions_[index].indices;
|
|
}
|
|
#endif // !defined(SWIGPYTHON)
|
|
/// Returns the penalty of the node disjunction of index 'index'.
|
|
int64_t GetDisjunctionPenalty(DisjunctionIndex index) const {
|
|
return disjunctions_[index].value.penalty;
|
|
}
|
|
/// Returns the maximum number of possible active nodes of the node
|
|
/// disjunction of index 'index'.
|
|
int64_t GetDisjunctionMaxCardinality(DisjunctionIndex index) const {
|
|
return disjunctions_[index].value.max_cardinality;
|
|
}
|
|
/// Returns the number of node disjunctions in the model.
|
|
int GetNumberOfDisjunctions() const { return disjunctions_.size(); }
|
|
/// Returns true if the model contains mandatory disjunctions (ones with
|
|
/// kNoPenalty as penalty).
|
|
bool HasMandatoryDisjunctions() const;
|
|
/// Returns true if the model contains at least one disjunction which is
|
|
/// constrained by its max_cardinality.
|
|
bool HasMaxCardinalityConstrainedDisjunctions() const;
|
|
/// Returns the list of all perfect binary disjunctions, as pairs of variable
|
|
/// indices: a disjunction is "perfect" when its variables do not appear in
|
|
/// any other disjunction. Each pair is sorted (lowest variable index first),
|
|
/// and the output vector is also sorted (lowest pairs first).
|
|
std::vector<std::pair<int64_t, int64_t>> GetPerfectBinaryDisjunctions() const;
|
|
/// SPECIAL: Makes the solver ignore all the disjunctions whose active
|
|
/// variables are all trivially zero (i.e. Max() == 0), by setting their
|
|
/// max_cardinality to 0.
|
|
/// This can be useful when using the BaseBinaryDisjunctionNeighborhood
|
|
/// operators, in the context of arc-based routing.
|
|
void IgnoreDisjunctionsAlreadyForcedToZero();
|
|
|
|
/// Adds a soft constraint to force a set of variable indices to be on the
|
|
/// same vehicle. If all nodes are not on the same vehicle, each extra vehicle
|
|
/// used adds 'cost' to the cost function.
|
|
void AddSoftSameVehicleConstraint(const std::vector<int64_t>& indices,
|
|
int64_t cost);
|
|
|
|
/// Sets the vehicles which can visit a given node. If the node is in a
|
|
/// disjunction, this will not prevent it from being unperformed.
|
|
/// Specifying an empty vector of vehicles has no effect (all vehicles
|
|
/// will be allowed to visit the node).
|
|
void SetAllowedVehiclesForIndex(const std::vector<int>& vehicles,
|
|
int64_t index);
|
|
|
|
/// Returns true if a vehicle is allowed to visit a given node.
|
|
bool IsVehicleAllowedForIndex(int vehicle, int64_t index) {
|
|
return allowed_vehicles_[index].empty() ||
|
|
allowed_vehicles_[index].find(vehicle) !=
|
|
allowed_vehicles_[index].end();
|
|
}
|
|
|
|
/// Notifies that index1 and index2 form a pair of nodes which should belong
|
|
/// to the same route. This methods helps the search find better solutions,
|
|
/// especially in the local search phase.
|
|
/// It should be called each time you have an equality constraint linking
|
|
/// the vehicle variables of two node (including for instance pickup and
|
|
/// delivery problems):
|
|
/// Solver* const solver = routing.solver();
|
|
/// int64_t index1 = manager.NodeToIndex(node1);
|
|
/// int64_t index2 = manager.NodeToIndex(node2);
|
|
/// solver->AddConstraint(solver->MakeEquality(
|
|
/// routing.VehicleVar(index1),
|
|
/// routing.VehicleVar(index2)));
|
|
/// routing.AddPickupAndDelivery(index1, index2);
|
|
///
|
|
// TODO(user): Remove this when model introspection detects linked nodes.
|
|
void AddPickupAndDelivery(int64_t pickup, int64_t delivery);
|
|
/// Same as AddPickupAndDelivery but notifying that the performed node from
|
|
/// the disjunction of index 'pickup_disjunction' is on the same route as the
|
|
/// performed node from the disjunction of index 'delivery_disjunction'.
|
|
void AddPickupAndDeliverySets(DisjunctionIndex pickup_disjunction,
|
|
DisjunctionIndex delivery_disjunction);
|
|
// clang-format off
|
|
/// Returns pairs for which the node is a pickup; the first element of each
|
|
/// pair is the index in the pickup and delivery pairs list in which the
|
|
/// pickup appears, the second element is its index in the pickups list.
|
|
const std::vector<std::pair<int, int> >&
|
|
GetPickupIndexPairs(int64_t node_index) const;
|
|
/// Same as above for deliveries.
|
|
const std::vector<std::pair<int, int> >&
|
|
GetDeliveryIndexPairs(int64_t node_index) const;
|
|
// clang-format on
|
|
|
|
/// Sets the Pickup and delivery policy of all vehicles. It is equivalent to
|
|
/// calling SetPickupAndDeliveryPolicyOfVehicle on all vehicles.
|
|
void SetPickupAndDeliveryPolicyOfAllVehicles(PickupAndDeliveryPolicy policy);
|
|
void SetPickupAndDeliveryPolicyOfVehicle(PickupAndDeliveryPolicy policy,
|
|
int vehicle);
|
|
PickupAndDeliveryPolicy GetPickupAndDeliveryPolicyOfVehicle(
|
|
int vehicle) const;
|
|
/// Returns the number of non-start/end nodes which do not appear in a
|
|
/// pickup/delivery pair.
|
|
|
|
int GetNumOfSingletonNodes() const;
|
|
|
|
#ifndef SWIG
|
|
/// Returns pickup and delivery pairs currently in the model.
|
|
const IndexPairs& GetPickupAndDeliveryPairs() const {
|
|
return pickup_delivery_pairs_;
|
|
}
|
|
const std::vector<std::pair<DisjunctionIndex, DisjunctionIndex>>&
|
|
GetPickupAndDeliveryDisjunctions() const {
|
|
return pickup_delivery_disjunctions_;
|
|
}
|
|
/// Returns implicit pickup and delivery pairs currently in the model.
|
|
/// Pairs are implicit if they are not linked by a pickup and delivery
|
|
/// constraint but that for a given unary dimension, the first element of the
|
|
/// pair has a positive demand d, and the second element has a demand of -d.
|
|
const IndexPairs& GetImplicitUniquePickupAndDeliveryPairs() const {
|
|
DCHECK(closed_);
|
|
return implicit_pickup_delivery_pairs_without_alternatives_;
|
|
}
|
|
#endif // SWIG
|
|
/// Set the node visit types and incompatibilities/requirements between the
|
|
/// types (see below).
|
|
///
|
|
/// NOTE: Before adding any incompatibilities and/or requirements on types:
|
|
/// 1) All corresponding node types must have been set.
|
|
/// 2) CloseVisitTypes() must be called so all containers are resized
|
|
/// accordingly.
|
|
///
|
|
/// The following enum is used to describe how a node with a given type 'T'
|
|
/// impacts the number of types 'T' on the route when visited, and thus
|
|
/// determines how temporal incompatibilities and requirements take effect.
|
|
enum VisitTypePolicy {
|
|
/// When visited, the number of types 'T' on the vehicle increases by one.
|
|
TYPE_ADDED_TO_VEHICLE,
|
|
/// When visited, one instance of type 'T' previously added to the route
|
|
/// (TYPE_ADDED_TO_VEHICLE), if any, is removed from the vehicle.
|
|
/// If the type was not previously added to the route or all added instances
|
|
/// have already been removed, this visit has no effect on the types.
|
|
ADDED_TYPE_REMOVED_FROM_VEHICLE,
|
|
/// With the following policy, the visit enforces that type 'T' is
|
|
/// considered on the route from its start until this node is visited.
|
|
TYPE_ON_VEHICLE_UP_TO_VISIT,
|
|
/// The visit doesn't have an impact on the number of types 'T' on the
|
|
/// route, as it's (virtually) added and removed directly.
|
|
/// This policy can be used for visits which are part of an incompatibility
|
|
/// or requirement set without affecting the type count on the route.
|
|
TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED
|
|
};
|
|
// TODO(user): Support multiple visit types per node?
|
|
void SetVisitType(int64_t index, int type, VisitTypePolicy type_policy);
|
|
int GetVisitType(int64_t index) const;
|
|
const std::vector<int>& GetSingleNodesOfType(int type) const;
|
|
const std::vector<int>& GetPairIndicesOfType(int type) const;
|
|
VisitTypePolicy GetVisitTypePolicy(int64_t 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_; }
|
|
#ifndef SWIG
|
|
const std::vector<std::vector<int>>& GetTopologicallySortedVisitTypes()
|
|
const {
|
|
DCHECK(closed_);
|
|
return topologically_sorted_visit_types_;
|
|
}
|
|
#endif // SWIG
|
|
/// Incompatibilities:
|
|
/// Two nodes with "hard" incompatible types cannot share the same route at
|
|
/// all, while with a "temporal" incompatibility they can't be on the same
|
|
/// route at the same time.
|
|
void AddHardTypeIncompatibility(int type1, int type2);
|
|
void AddTemporalTypeIncompatibility(int type1, int type2);
|
|
/// Returns visit types incompatible with a given type.
|
|
const absl::flat_hash_set<int>& GetHardTypeIncompatibilitiesOfType(
|
|
int type) const;
|
|
const absl::flat_hash_set<int>& GetTemporalTypeIncompatibilitiesOfType(
|
|
int type) const;
|
|
/// Returns true iff any hard (resp. temporal) type incompatibilities have
|
|
/// been added to the model.
|
|
bool HasHardTypeIncompatibilities() const {
|
|
return has_hard_type_incompatibilities_;
|
|
}
|
|
bool HasTemporalTypeIncompatibilities() const {
|
|
return has_temporal_type_incompatibilities_;
|
|
}
|
|
/// Requirements:
|
|
/// 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 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 depends on type_R when adding type_D, any node_D of type_D and
|
|
/// VisitTypePolicy TYPE_ADDED_TO_VEHICLE or
|
|
/// TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED requires at least one type_R on its
|
|
/// vehicle at the time node_D is visited.
|
|
void AddRequiredTypeAlternativesWhenAddingType(
|
|
int dependent_type, absl::flat_hash_set<int> required_type_alternatives);
|
|
/// The following requirements apply when visiting dependent nodes that remove
|
|
/// their type from the route, i.e. type_R must be on the vehicle when type_D
|
|
/// of VisitTypePolicy ADDED_TYPE_REMOVED_FROM_VEHICLE,
|
|
/// TYPE_ON_VEHICLE_UP_TO_VISIT or TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED is
|
|
/// visited.
|
|
void AddRequiredTypeAlternativesWhenRemovingType(
|
|
int dependent_type, absl::flat_hash_set<int> required_type_alternatives);
|
|
// clang-format off
|
|
/// Returns the set of same-vehicle requirement alternatives for the given
|
|
/// type.
|
|
const std::vector<absl::flat_hash_set<int> >&
|
|
GetSameVehicleRequiredTypeAlternativesOfType(int type) const;
|
|
/// Returns the set of requirement alternatives when adding the given type.
|
|
const std::vector<absl::flat_hash_set<int> >&
|
|
GetRequiredTypeAlternativesWhenAddingType(int type) const;
|
|
/// Returns the set of requirement alternatives when removing the given type.
|
|
const std::vector<absl::flat_hash_set<int> >&
|
|
GetRequiredTypeAlternativesWhenRemovingType(int type) const;
|
|
// clang-format on
|
|
/// 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_;
|
|
}
|
|
|
|
/// Returns true iff the model has any incompatibilities or requirements set
|
|
/// on node types.
|
|
bool HasTypeRegulations() const {
|
|
return HasTemporalTypeIncompatibilities() ||
|
|
HasHardTypeIncompatibilities() || HasSameVehicleTypeRequirements() ||
|
|
HasTemporalTypeRequirements();
|
|
}
|
|
|
|
/// Get the "unperformed" penalty of a node. This is only well defined if the
|
|
/// node is only part of a single Disjunction, and that disjunction has a
|
|
/// penalty. For forced active nodes returns max int64_t. In all other cases,
|
|
/// this returns 0.
|
|
int64_t UnperformedPenalty(int64_t var_index) const;
|
|
/// Same as above except that it returns default_value instead of 0 when
|
|
/// penalty is not well defined (default value is passed as first argument to
|
|
/// simplify the usage of the method in a callback).
|
|
int64_t UnperformedPenaltyOrValue(int64_t default_value,
|
|
int64_t var_index) const;
|
|
/// Returns the variable index of the first starting or ending node of all
|
|
/// routes. If all routes start and end at the same node (single depot), this
|
|
/// is the node returned.
|
|
int64_t GetDepot() const;
|
|
|
|
/// Constrains the maximum number of active vehicles, aka the number of
|
|
/// vehicles which do not have an empty route. For instance, this can be used
|
|
/// to limit the number of routes in the case where there are fewer drivers
|
|
/// than vehicles and that the fleet of vehicle is heterogeneous.
|
|
void SetMaximumNumberOfActiveVehicles(int max_active_vehicles) {
|
|
max_active_vehicles_ = max_active_vehicles;
|
|
}
|
|
/// Returns the maximum number of active vehicles.
|
|
int GetMaximumNumberOfActiveVehicles() const { return max_active_vehicles_; }
|
|
/// Sets the cost function of the model such that the cost of a segment of a
|
|
/// route between node 'from' and 'to' is evaluator(from, to), whatever the
|
|
/// route or vehicle performing the route.
|
|
void SetArcCostEvaluatorOfAllVehicles(int evaluator_index);
|
|
/// Sets the cost function for a given vehicle route.
|
|
void SetArcCostEvaluatorOfVehicle(int evaluator_index, int vehicle);
|
|
/// Sets the fixed cost of all vehicle routes. It is equivalent to calling
|
|
/// SetFixedCostOfVehicle on all vehicle routes.
|
|
void SetFixedCostOfAllVehicles(int64_t cost);
|
|
/// Sets the fixed cost of one vehicle route.
|
|
void SetFixedCostOfVehicle(int64_t cost, int vehicle);
|
|
/// Returns the route fixed cost taken into account if the route of the
|
|
/// vehicle is not empty, aka there's at least one node on the route other
|
|
/// than the first and last nodes.
|
|
int64_t GetFixedCostOfVehicle(int vehicle) const;
|
|
|
|
/// The following methods set the linear and quadratic cost factors of
|
|
/// vehicles (must be positive values). The default value of these parameters
|
|
/// is zero for all vehicles.
|
|
///
|
|
/// When set, the cost_ of the model will contain terms aiming at reducing the
|
|
/// number of vehicles used in the model, by adding the following to the
|
|
/// objective for every vehicle v:
|
|
/// INDICATOR(v used in the model) *
|
|
/// [linear_cost_factor_of_vehicle_[v]
|
|
/// - quadratic_cost_factor_of_vehicle_[v]*(square of length of route v)]
|
|
/// i.e. for every used vehicle, we add the linear factor as fixed cost, and
|
|
/// subtract the square of the route length multiplied by the quadratic
|
|
/// factor. This second term aims at making the routes as dense as possible.
|
|
///
|
|
/// Sets the linear and quadratic cost factor of all vehicles.
|
|
void SetAmortizedCostFactorsOfAllVehicles(int64_t linear_cost_factor,
|
|
int64_t quadratic_cost_factor);
|
|
/// Sets the linear and quadratic cost factor of the given vehicle.
|
|
void SetAmortizedCostFactorsOfVehicle(int64_t linear_cost_factor,
|
|
int64_t quadratic_cost_factor,
|
|
int vehicle);
|
|
|
|
const std::vector<int64_t>& GetAmortizedLinearCostFactorOfVehicles() const {
|
|
return linear_cost_factor_of_vehicle_;
|
|
}
|
|
const std::vector<int64_t>& GetAmortizedQuadraticCostFactorOfVehicles()
|
|
const {
|
|
return quadratic_cost_factor_of_vehicle_;
|
|
}
|
|
|
|
void SetVehicleUsedWhenEmpty(bool is_used, int vehicle) {
|
|
DCHECK_LT(vehicle, vehicles_);
|
|
vehicle_used_when_empty_[vehicle] = is_used;
|
|
}
|
|
|
|
bool IsVehicleUsedWhenEmpty(int vehicle) const {
|
|
DCHECK_LT(vehicle, vehicles_);
|
|
return vehicle_used_when_empty_[vehicle];
|
|
}
|
|
|
|
/// Gets/sets the evaluator used during the search. Only relevant when
|
|
/// RoutingSearchParameters.first_solution_strategy = EVALUATOR_STRATEGY.
|
|
#ifndef SWIG
|
|
const Solver::IndexEvaluator2& first_solution_evaluator() const {
|
|
return first_solution_evaluator_;
|
|
}
|
|
#endif
|
|
/// Takes ownership of evaluator.
|
|
void SetFirstSolutionEvaluator(Solver::IndexEvaluator2 evaluator) {
|
|
first_solution_evaluator_ = std::move(evaluator);
|
|
}
|
|
/// Adds a local search operator to the set of operators used to solve the
|
|
/// vehicle routing problem.
|
|
void AddLocalSearchOperator(LocalSearchOperator* ls_operator);
|
|
/// Adds a search monitor to the search used to solve the routing model.
|
|
void AddSearchMonitor(SearchMonitor* const monitor);
|
|
/// Adds a callback called each time a solution is found during the search.
|
|
/// This is a shortcut to creating a monitor to call the callback on
|
|
/// AtSolution() and adding it with AddSearchMonitor.
|
|
void AddAtSolutionCallback(std::function<void()> callback);
|
|
/// Adds a variable to minimize in the solution finalizer. The solution
|
|
/// finalizer is called each time a solution is found during the search and
|
|
/// allows to instantiate secondary variables (such as dimension cumul
|
|
/// variables).
|
|
void AddVariableMinimizedByFinalizer(IntVar* var);
|
|
/// Adds a variable to maximize in the solution finalizer (see above for
|
|
/// information on the solution finalizer).
|
|
void AddVariableMaximizedByFinalizer(IntVar* var);
|
|
/// Adds a variable to minimize in the solution finalizer, with a weighted
|
|
/// priority: the higher the more priority it has.
|
|
void AddWeightedVariableMinimizedByFinalizer(IntVar* var, int64_t cost);
|
|
/// Adds a variable to maximize in the solution finalizer, with a weighted
|
|
/// priority: the higher the more priority it has.
|
|
void AddWeightedVariableMaximizedByFinalizer(IntVar* var, int64_t cost);
|
|
/// Add a variable to set the closest possible to the target value in the
|
|
/// solution finalizer.
|
|
void AddVariableTargetToFinalizer(IntVar* var, int64_t target);
|
|
/// Same as above with a weighted priority: the higher the cost, the more
|
|
/// priority it has to be set close to the target value.
|
|
void AddWeightedVariableTargetToFinalizer(IntVar* var, int64_t target,
|
|
int64_t cost);
|
|
/// Closes the current routing model; after this method is called, no
|
|
/// modification to the model can be done, but RoutesToAssignment becomes
|
|
/// available. Note that CloseModel() is automatically called by Solve() and
|
|
/// other methods that produce solution.
|
|
/// This is equivalent to calling
|
|
/// CloseModelWithParameters(DefaultRoutingSearchParameters()).
|
|
void CloseModel();
|
|
/// Same as above taking search parameters (as of 10/2015 some the parameters
|
|
/// have to be set when closing the model).
|
|
void CloseModelWithParameters(
|
|
const RoutingSearchParameters& search_parameters);
|
|
/// Solves the current routing model; closes the current model.
|
|
/// This is equivalent to calling
|
|
/// SolveWithParameters(DefaultRoutingSearchParameters())
|
|
/// or
|
|
/// SolveFromAssignmentWithParameters(assignment,
|
|
/// DefaultRoutingSearchParameters()).
|
|
const Assignment* Solve(const Assignment* assignment = nullptr);
|
|
/// Solves the current routing model with the given parameters. If 'solutions'
|
|
/// is specified, it will contain the k best solutions found during the search
|
|
/// (from worst to best, including the one returned by this method), where k
|
|
/// corresponds to the 'number_of_solutions_to_collect' in
|
|
/// 'search_parameters'. Note that the Assignment returned by the method and
|
|
/// the ones in solutions are owned by the underlying solver and should not be
|
|
/// deleted.
|
|
const Assignment* SolveWithParameters(
|
|
const RoutingSearchParameters& search_parameters,
|
|
std::vector<const Assignment*>* solutions = nullptr);
|
|
/// Same as above, except that if assignment is not null, it will be used as
|
|
/// the initial solution.
|
|
const Assignment* SolveFromAssignmentWithParameters(
|
|
const Assignment* assignment,
|
|
const RoutingSearchParameters& search_parameters,
|
|
std::vector<const Assignment*>* solutions = nullptr);
|
|
/// Same as above but will try all assignments in order as first solutions
|
|
/// until one succeeds.
|
|
const Assignment* SolveFromAssignmentsWithParameters(
|
|
const std::vector<const Assignment*>& assignments,
|
|
const RoutingSearchParameters& search_parameters,
|
|
std::vector<const Assignment*>* solutions = nullptr);
|
|
/// Given a "source_model" and its "source_assignment", resets
|
|
/// "target_assignment" with the IntVar variables (nexts_, and vehicle_vars_
|
|
/// if costs aren't homogeneous across vehicles) of "this" model, with the
|
|
/// values set according to those in "other_assignment".
|
|
/// The objective_element of target_assignment is set to this->cost_.
|
|
void SetAssignmentFromOtherModelAssignment(
|
|
Assignment* target_assignment, const RoutingModel* source_model,
|
|
const Assignment* source_assignment);
|
|
/// Computes a lower bound to the routing problem solving a linear assignment
|
|
/// problem. The routing model must be closed before calling this method.
|
|
/// Note that problems with node disjunction constraints (including optional
|
|
/// nodes) and non-homogenous costs are not supported (the method returns 0 in
|
|
/// these cases).
|
|
// TODO(user): Add support for non-homogeneous costs and disjunctions.
|
|
int64_t ComputeLowerBound();
|
|
/// Returns the current status of the routing model.
|
|
Status status() const { return status_; }
|
|
/// Returns the value of the internal enable_deep_serialization_ parameter.
|
|
bool enable_deep_serialization() const { return enable_deep_serialization_; }
|
|
/// Applies a lock chain to the next search. 'locks' represents an ordered
|
|
/// vector of nodes representing a partial route which will be fixed during
|
|
/// the next search; it will constrain next variables such that:
|
|
/// next[locks[i]] == locks[i+1].
|
|
///
|
|
/// Returns the next variable at the end of the locked chain; this variable is
|
|
/// not locked. An assignment containing the locks can be obtained by calling
|
|
/// PreAssignment().
|
|
IntVar* ApplyLocks(const std::vector<int64_t>& locks);
|
|
/// Applies lock chains to all vehicles to the next search, such that locks[p]
|
|
/// is the lock chain for route p. Returns false if the locks do not contain
|
|
/// valid routes; expects that the routes do not contain the depots,
|
|
/// i.e. there are empty vectors in place of empty routes.
|
|
/// If close_routes is set to true, adds the end nodes to the route of each
|
|
/// vehicle and deactivates other nodes.
|
|
/// An assignment containing the locks can be obtained by calling
|
|
/// PreAssignment().
|
|
bool ApplyLocksToAllVehicles(const std::vector<std::vector<int64_t>>& locks,
|
|
bool close_routes);
|
|
/// Returns an assignment used to fix some of the variables of the problem.
|
|
/// In practice, this assignment locks partial routes of the problem. This
|
|
/// can be used in the context of locking the parts of the routes which have
|
|
/// already been driven in online routing problems.
|
|
const Assignment* const PreAssignment() const { return preassignment_; }
|
|
Assignment* MutablePreAssignment() { return preassignment_; }
|
|
/// Writes the current solution to a file containing an AssignmentProto.
|
|
/// Returns false if the file cannot be opened or if there is no current
|
|
/// solution.
|
|
bool WriteAssignment(const std::string& file_name) const;
|
|
/// Reads an assignment from a file and returns the current solution.
|
|
/// Returns nullptr if the file cannot be opened or if the assignment is not
|
|
/// valid.
|
|
Assignment* ReadAssignment(const std::string& file_name);
|
|
/// Restores an assignment as a solution in the routing model and returns the
|
|
/// new solution. Returns nullptr if the assignment is not valid.
|
|
Assignment* RestoreAssignment(const Assignment& solution);
|
|
/// Restores the routes as the current solution. Returns nullptr if the
|
|
/// solution cannot be restored (routes do not contain a valid solution). Note
|
|
/// that calling this method will run the solver to assign values to the
|
|
/// dimension variables; this may take considerable amount of time, especially
|
|
/// when using dimensions with slack.
|
|
Assignment* ReadAssignmentFromRoutes(
|
|
const std::vector<std::vector<int64_t>>& routes,
|
|
bool ignore_inactive_indices);
|
|
/// Fills an assignment from a specification of the routes of the
|
|
/// vehicles. The routes are specified as lists of variable indices that
|
|
/// appear on the routes of the vehicles. The indices of the outer vector in
|
|
/// 'routes' correspond to vehicles IDs, the inner vector contains the
|
|
/// variable indices on the routes for the given vehicle. The inner vectors
|
|
/// must not contain the start and end indices, as these are determined by the
|
|
/// routing model. Sets the value of NextVars in the assignment, adding the
|
|
/// variables to the assignment if necessary. The method does not touch other
|
|
/// variables in the assignment. The method can only be called after the model
|
|
/// is closed. With ignore_inactive_indices set to false, this method will
|
|
/// fail (return nullptr) in case some of the route contain indices that are
|
|
/// deactivated in the model; when set to true, these indices will be
|
|
/// skipped. Returns true if routes were successfully
|
|
/// loaded. However, such assignment still might not be a valid
|
|
/// solution to the routing problem due to more complex constraints;
|
|
/// it is advisible to call solver()->CheckSolution() afterwards.
|
|
bool RoutesToAssignment(const std::vector<std::vector<int64_t>>& routes,
|
|
bool ignore_inactive_indices, bool close_routes,
|
|
Assignment* const assignment) const;
|
|
/// Converts the solution in the given assignment to routes for all vehicles.
|
|
/// Expects that assignment contains a valid solution (i.e. routes for all
|
|
/// vehicles end with an end index for that vehicle).
|
|
void AssignmentToRoutes(
|
|
const Assignment& assignment,
|
|
std::vector<std::vector<int64_t>>* const routes) const;
|
|
/// Converts the solution in the given assignment to routes for all vehicles.
|
|
/// If the returned vector is route_indices, route_indices[i][j] is the index
|
|
/// for jth location visited on route i. Note that contrary to
|
|
/// AssignmentToRoutes, the vectors do include start and end locations.
|
|
#ifndef SWIG
|
|
std::vector<std::vector<int64_t>> GetRoutesFromAssignment(
|
|
const Assignment& assignment);
|
|
#endif
|
|
/// Returns a compacted version of the given assignment, in which all vehicles
|
|
/// with id lower or equal to some N have non-empty routes, and all vehicles
|
|
/// with id greater than N have empty routes. Does not take ownership of the
|
|
/// returned object.
|
|
/// If found, the cost of the compact assignment is the same as in the
|
|
/// original assignment and it preserves the values of 'active' variables.
|
|
/// Returns nullptr if a compact assignment was not found.
|
|
/// This method only works in homogenous mode, and it only swaps equivalent
|
|
/// vehicles (vehicles with the same start and end nodes). When creating the
|
|
/// compact assignment, the empty plan is replaced by the route assigned to
|
|
/// the compatible vehicle with the highest id. Note that with more complex
|
|
/// constraints on vehicle variables, this method might fail even if a compact
|
|
/// solution exists.
|
|
/// This method changes the vehicle and dimension variables as necessary.
|
|
/// While compacting the solution, only basic checks on vehicle variables are
|
|
/// performed; if one of these checks fails no attempts to repair it are made
|
|
/// (instead, the method returns nullptr).
|
|
Assignment* CompactAssignment(const Assignment& assignment) const;
|
|
/// Same as CompactAssignment() but also checks the validity of the final
|
|
/// compact solution; if it is not valid, no attempts to repair it are made
|
|
/// (instead, the method returns nullptr).
|
|
Assignment* CompactAndCheckAssignment(const Assignment& assignment) const;
|
|
/// 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 with an optimizer in
|
|
/// local/global_dimension_optimizers_, 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 cumul cost.
|
|
/// - 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* PackCumulsOfOptimizerDimensionsFromAssignment(
|
|
const Assignment* original_assignment, absl::Duration duration_limit,
|
|
bool* time_limit_was_reached = nullptr);
|
|
/// Contains the information needed by the solver to optimize a dimension's
|
|
/// cumuls with travel-start dependent transit values.
|
|
struct RouteDimensionTravelInfo {
|
|
/// Contains the information for a single transition on the route.
|
|
struct TransitionInfo {
|
|
/// The following struct defines a piecewise linear formulation, with
|
|
/// int64_t values for the "anchor" x and y values, and potential double
|
|
/// values for the slope of each linear function.
|
|
// TODO(user): Adjust the inlined vector sizes based on experiments.
|
|
struct PiecewiseLinearFormulation {
|
|
/// The set of *increasing* anchor cumul values for the interpolation.
|
|
absl::InlinedVector<int64_t, 8> x_anchors;
|
|
/// The y values used for the interpolation:
|
|
/// For any x anchor value, let i be an index such that
|
|
/// x_anchors[i] ≤ x < x_anchors[i+1], then the y value for x is
|
|
/// y_anchors[i] * (1-λ) + y_anchors[i+1] * λ, with
|
|
/// λ = (x - x_anchors[i]) / (x_anchors[i+1] - x_anchors[i]).
|
|
absl::InlinedVector<int64_t, 8> y_anchors;
|
|
|
|
std::string DebugString(std::string line_prefix = "") const;
|
|
};
|
|
|
|
/// Models the (real) travel value Tᵣ, for this transition based on the
|
|
/// departure value of the travel.
|
|
PiecewiseLinearFormulation travel_start_dependent_travel;
|
|
|
|
/// travel_compression_cost models the cost of the difference between the
|
|
/// (real) travel value Tᵣ given by travel_start_dependent_travel and the
|
|
/// compressed travel value considered in the scheduling.
|
|
PiecewiseLinearFormulation travel_compression_cost;
|
|
|
|
/// The parts of the transit which occur pre/post travel between the
|
|
/// nodes. The total transit between the two nodes i and j is
|
|
/// = pre_travel_transit_value + travel(i, j) + post_travel_transit_value.
|
|
int64_t pre_travel_transit_value;
|
|
int64_t post_travel_transit_value;
|
|
|
|
/// The hard lower bound of the compressed travel value that will be
|
|
/// enforced by the scheduling module.
|
|
int64_t compressed_travel_value_lower_bound;
|
|
|
|
/// The hard upper bound of the (real) travel value Tᵣ (see
|
|
/// above). This value should be chosen so as to prevent
|
|
/// the overall cost of the model
|
|
/// (dimension costs + travel_compression_cost) to overflow.
|
|
int64_t travel_value_upper_bound;
|
|
|
|
std::string DebugString(std::string line_prefix = "") const;
|
|
};
|
|
|
|
/// For each node #i on the route, transition_info[i] contains the relevant
|
|
/// information for the travel between nodes #i and #(i + 1) on the route.
|
|
std::vector<TransitionInfo> transition_info;
|
|
/// The cost per unit of travel for this vehicle.
|
|
int64_t travel_cost_coefficient;
|
|
|
|
std::string DebugString(std::string line_prefix = "") const;
|
|
};
|
|
|
|
#ifndef SWIG
|
|
// TODO(user): Revisit if coordinates are added to the RoutingModel class.
|
|
void SetSweepArranger(SweepArranger* sweep_arranger);
|
|
/// Returns the sweep arranger to be used by routing heuristics.
|
|
SweepArranger* sweep_arranger() const;
|
|
#endif
|
|
/// Adds a custom local search filter to the list of filters used to speed up
|
|
/// local search by pruning unfeasible variable assignments.
|
|
/// Calling this method after the routing model has been closed (CloseModel()
|
|
/// or Solve() has been called) has no effect.
|
|
/// The routing model does not take ownership of the filter.
|
|
void AddLocalSearchFilter(LocalSearchFilter* filter) {
|
|
CHECK(filter != nullptr);
|
|
if (closed_) {
|
|
LOG(WARNING) << "Model is closed, filter addition will be ignored.";
|
|
}
|
|
extra_filters_.push_back({filter, LocalSearchFilterManager::kRelax});
|
|
extra_filters_.push_back({filter, LocalSearchFilterManager::kAccept});
|
|
}
|
|
|
|
/// Model inspection.
|
|
/// Returns the variable index of the starting node of a vehicle route.
|
|
int64_t Start(int vehicle) const { return paths_metadata_.Starts()[vehicle]; }
|
|
/// Returns the variable index of the ending node of a vehicle route.
|
|
int64_t End(int vehicle) const { return paths_metadata_.Ends()[vehicle]; }
|
|
/// Returns true if 'index' represents the first node of a route.
|
|
bool IsStart(int64_t index) const { return paths_metadata_.IsStart(index); }
|
|
/// Returns true if 'index' represents the last node of a route.
|
|
bool IsEnd(int64_t index) const { return paths_metadata_.IsEnd(index); }
|
|
/// Returns the vehicle of the given start/end index, and -1 if the given
|
|
/// index is not a vehicle start/end.
|
|
int VehicleIndex(int64_t index) const {
|
|
return paths_metadata_.GetPath(index);
|
|
}
|
|
/// Assignment inspection
|
|
/// Returns the variable index of the node directly after the node
|
|
/// corresponding to 'index' in 'assignment'.
|
|
int64_t Next(const Assignment& assignment, int64_t index) const;
|
|
/// Returns true if the route of 'vehicle' is non empty in 'assignment'.
|
|
bool IsVehicleUsed(const Assignment& assignment, int vehicle) const;
|
|
|
|
#if !defined(SWIGPYTHON)
|
|
/// Returns all next variables of the model, such that Nexts(i) is the next
|
|
/// variable of the node corresponding to i.
|
|
const std::vector<IntVar*>& Nexts() const { return nexts_; }
|
|
/// Returns all vehicle variables of the model, such that VehicleVars(i) is
|
|
/// the vehicle variable of the node corresponding to i.
|
|
const std::vector<IntVar*>& VehicleVars() const { return vehicle_vars_; }
|
|
/// Returns vehicle resource variables for a given resource group, such that
|
|
/// ResourceVars(r_g)[v] is the resource variable for vehicle 'v' in resource
|
|
/// group 'r_g'.
|
|
const std::vector<IntVar*>& ResourceVars(int resource_group) const {
|
|
return resource_vars_[resource_group];
|
|
}
|
|
#endif /// !defined(SWIGPYTHON)
|
|
/// Returns the next variable of the node corresponding to index. Note that
|
|
/// NextVar(index) == index is equivalent to ActiveVar(index) == 0.
|
|
IntVar* NextVar(int64_t index) const { return nexts_[index]; }
|
|
/// Returns the active variable of the node corresponding to index.
|
|
IntVar* ActiveVar(int64_t index) const { return active_[index]; }
|
|
/// Returns the active variable of the vehicle. It will be equal to 1 iff the
|
|
/// route of the vehicle is not empty, 0 otherwise.
|
|
IntVar* ActiveVehicleVar(int vehicle) const {
|
|
return vehicle_active_[vehicle];
|
|
}
|
|
/// Returns the variable specifying whether or not the given vehicle route is
|
|
/// considered for costs and constraints. It will be equal to 1 iff the route
|
|
/// of the vehicle is not empty OR vehicle_used_when_empty_[vehicle] is true.
|
|
IntVar* VehicleRouteConsideredVar(int vehicle) const {
|
|
return vehicle_route_considered_[vehicle];
|
|
}
|
|
/// Returns the vehicle variable of the node corresponding to index. Note that
|
|
/// VehicleVar(index) == -1 is equivalent to ActiveVar(index) == 0.
|
|
IntVar* VehicleVar(int64_t index) const { return vehicle_vars_[index]; }
|
|
/// Returns the resource variable for the given vehicle index in the given
|
|
/// resource group. If a vehicle doesn't require a resource from the
|
|
/// corresponding resource group, then ResourceVar(v, r_g) == -1.
|
|
IntVar* ResourceVar(int vehicle, int resource_group) const {
|
|
DCHECK_LT(resource_group, resource_vars_.size());
|
|
DCHECK_LT(vehicle, resource_vars_[resource_group].size());
|
|
return resource_vars_[resource_group][vehicle];
|
|
}
|
|
/// Returns the global cost variable which is being minimized.
|
|
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_t GetArcCostForVehicle(int64_t from_index, int64_t to_index,
|
|
int64_t 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_t GetHomogeneousCost(int64_t from_index, int64_t 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_t GetArcCostForFirstSolution(int64_t from_index,
|
|
int64_t 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
|
|
/// returned cost won't necessarily be zero: only some of the components
|
|
/// of the cost that depend on the cost class will be omited. See the code
|
|
/// for details.
|
|
int64_t GetArcCostForClass(int64_t from_index, int64_t to_index,
|
|
int64_t /*CostClassIndex*/ cost_class_index) const;
|
|
/// Get the cost class index of the given vehicle.
|
|
CostClassIndex GetCostClassIndexOfVehicle(int64_t vehicle) const {
|
|
DCHECK(closed_);
|
|
DCHECK_GE(vehicle, 0);
|
|
DCHECK_LT(vehicle, cost_class_index_of_vehicle_.size());
|
|
DCHECK_GE(cost_class_index_of_vehicle_[vehicle], 0);
|
|
return cost_class_index_of_vehicle_[vehicle];
|
|
}
|
|
/// Returns true iff the model contains a vehicle with the given
|
|
/// cost_class_index.
|
|
bool HasVehicleWithCostClassIndex(CostClassIndex cost_class_index) const {
|
|
DCHECK(closed_);
|
|
if (cost_class_index == kCostClassIndexOfZeroCost) {
|
|
return has_vehicle_with_zero_cost_class_;
|
|
}
|
|
return cost_class_index < cost_classes_.size();
|
|
}
|
|
/// Returns the number of different cost classes in the model.
|
|
int GetCostClassesCount() const { return cost_classes_.size(); }
|
|
/// Ditto, minus the 'always zero', built-in cost class.
|
|
int GetNonZeroCostClassesCount() const {
|
|
return std::max(0, GetCostClassesCount() - 1);
|
|
}
|
|
VehicleClassIndex GetVehicleClassIndexOfVehicle(int64_t vehicle) const {
|
|
DCHECK(closed_);
|
|
return vehicle_class_index_of_vehicle_[vehicle];
|
|
}
|
|
/// Returns a vehicle of the given vehicle class, and -1 if there are no
|
|
/// vehicles for this class.
|
|
int GetVehicleOfClass(VehicleClassIndex vehicle_class) const {
|
|
DCHECK(closed_);
|
|
const RoutingModel::VehicleTypeContainer& vehicle_type_container =
|
|
GetVehicleTypeContainer();
|
|
if (vehicle_class.value() >= GetVehicleClassesCount() ||
|
|
vehicle_type_container.vehicles_per_vehicle_class[vehicle_class.value()]
|
|
.empty()) {
|
|
return -1;
|
|
}
|
|
return vehicle_type_container
|
|
.vehicles_per_vehicle_class[vehicle_class.value()]
|
|
.front();
|
|
}
|
|
/// 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]];
|
|
}
|
|
|
|
const VehicleTypeContainer& GetVehicleTypeContainer() const {
|
|
DCHECK(closed_);
|
|
return vehicle_type_container_;
|
|
}
|
|
|
|
/// 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
|
|
/// - whether the destination node is mandatory
|
|
/// - whether the destination node is bound to the same vehicle as the source
|
|
/// - the "primary constrained" dimension (see SetPrimaryConstrainedDimension)
|
|
/// It then breaks ties using, in order:
|
|
/// - the arc cost (taking unperformed penalties into account)
|
|
/// - the size of the vehicle vars of "to1" and "to2" (lowest size wins)
|
|
/// - the value: the lowest value of the indices to1 and to2 wins.
|
|
/// See the .cc for details.
|
|
/// The more constrained arc is typically preferable when building a
|
|
/// first solution. This method is intended to be used as a callback for the
|
|
/// BestValueByComparisonSelector value selector.
|
|
/// Args:
|
|
/// from: the variable index of the source node
|
|
/// to1: the variable index of the first candidate destination node.
|
|
/// to2: the variable index of the second candidate destination node.
|
|
bool ArcIsMoreConstrainedThanArc(int64_t from, int64_t to1, int64_t to2);
|
|
/// Print some debugging information about an assignment, including the
|
|
/// feasible intervals of the CumulVar for dimension "dimension_to_print"
|
|
/// at each step of the routes.
|
|
/// If "dimension_to_print" is omitted, all dimensions will be printed.
|
|
std::string DebugOutputAssignment(
|
|
const Assignment& solution_assignment,
|
|
const std::string& dimension_to_print) const;
|
|
/// Returns a vector cumul_bounds, for which cumul_bounds[i][j] is a pair
|
|
/// containing the minimum and maximum of the CumulVar of the jth node on
|
|
/// route i.
|
|
/// - cumul_bounds[i][j].first is the minimum.
|
|
/// - cumul_bounds[i][j].second is the maximum.
|
|
#ifndef SWIG
|
|
std::vector<std::vector<std::pair<int64_t, int64_t>>> GetCumulBounds(
|
|
const Assignment& solution_assignment, const RoutingDimension& dimension);
|
|
#endif
|
|
/// Returns the underlying constraint solver. Can be used to add extra
|
|
/// constraints and/or modify search algorithms.
|
|
Solver* solver() const { return solver_.get(); }
|
|
|
|
/// Returns true if the search limit has been crossed.
|
|
bool CheckLimit() {
|
|
DCHECK(limit_ != nullptr);
|
|
return limit_->Check();
|
|
}
|
|
|
|
/// Returns the time left in the search limit.
|
|
absl::Duration RemainingTime() const {
|
|
DCHECK(limit_ != nullptr);
|
|
return limit_->AbsoluteSolverDeadline() - solver_->Now();
|
|
}
|
|
|
|
/// Sizes and indices
|
|
/// Returns the number of nodes in the model.
|
|
int nodes() const { return nodes_; }
|
|
/// Returns the number of vehicle routes in the model.
|
|
int vehicles() const { return vehicles_; }
|
|
/// Returns the number of next variables in the model.
|
|
int64_t Size() const { return nodes_ + vehicles_ - start_end_count_; }
|
|
|
|
/// Returns statistics on first solution search, number of decisions sent to
|
|
/// filters, number of decisions rejected by filters.
|
|
int64_t GetNumberOfDecisionsInFirstSolution(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
int64_t GetNumberOfRejectsInFirstSolution(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
/// Returns the automatic first solution strategy selected.
|
|
operations_research::FirstSolutionStrategy::Value
|
|
GetAutomaticFirstSolutionStrategy() const {
|
|
return automatic_first_solution_strategy_;
|
|
}
|
|
|
|
/// Returns true if a vehicle/node matching problem is detected.
|
|
bool IsMatchingModel() const;
|
|
|
|
/// Returns true if routes are interdependent. This means that any
|
|
/// modification to a route might impact another.
|
|
bool AreRoutesInterdependent(const RoutingSearchParameters& parameters) const;
|
|
|
|
#ifndef SWIG
|
|
/// Sets the callback returning the variable to use for the Tabu Search
|
|
/// metaheuristic.
|
|
using GetTabuVarsCallback =
|
|
std::function<std::vector<operations_research::IntVar*>(RoutingModel*)>;
|
|
|
|
void SetTabuVarsCallback(GetTabuVarsCallback tabu_var_callback);
|
|
#endif // SWIG
|
|
|
|
/// The next few members are in the public section only for testing purposes.
|
|
// TODO(user): Find a way to test and restrict the access at the same time.
|
|
///
|
|
/// MakeGuidedSlackFinalizer creates a DecisionBuilder for the slacks of a
|
|
/// dimension using a callback to choose which values to start with.
|
|
/// The finalizer works only when all next variables in the model have
|
|
/// been fixed. It has the following two characteristics:
|
|
/// 1. It follows the routes defined by the nexts variables when choosing a
|
|
/// variable to make a decision on.
|
|
/// 2. When it comes to choose a value for the slack of node i, the decision
|
|
/// builder first calls the callback with argument i, and supposingly the
|
|
/// returned value is x it creates decisions slack[i] = x, slack[i] = x +
|
|
/// 1, slack[i] = x - 1, slack[i] = x + 2, etc.
|
|
DecisionBuilder* MakeGuidedSlackFinalizer(
|
|
const RoutingDimension* dimension,
|
|
std::function<int64_t(int64_t)> initializer);
|
|
#ifndef SWIG
|
|
// TODO(user): MakeGreedyDescentLSOperator is too general for routing.h.
|
|
/// Perhaps move it to constraint_solver.h.
|
|
/// MakeGreedyDescentLSOperator creates a local search operator that tries to
|
|
/// improve the initial assignment by moving a logarithmically decreasing step
|
|
/// away in each possible dimension.
|
|
static std::unique_ptr<LocalSearchOperator> MakeGreedyDescentLSOperator(
|
|
std::vector<IntVar*> variables);
|
|
// Read access to currently registered search monitors.
|
|
const std::vector<SearchMonitor*>& GetSearchMonitors() const {
|
|
return monitors_;
|
|
}
|
|
#endif /// __SWIG__
|
|
/// MakeSelfDependentDimensionFinalizer is a finalizer for the slacks of a
|
|
/// self-dependent dimension. It makes an extensive use of the caches of the
|
|
/// state dependent transits.
|
|
/// In detail, MakeSelfDependentDimensionFinalizer returns a composition of a
|
|
/// local search decision builder with a greedy descent operator for the cumul
|
|
/// of the start of each route and a guided slack finalizer. Provided there
|
|
/// are no time windows and the maximum slacks are large enough, once the
|
|
/// cumul of the start of route is fixed, the guided finalizer can find
|
|
/// optimal values of the slacks for the rest of the route in time
|
|
/// proportional to the length of the route. Therefore the composed finalizer
|
|
/// generally works in time O(log(t)*n*m), where t is the latest possible
|
|
/// departute time, n is the number of nodes in the network and m is the
|
|
/// number of vehicles.
|
|
DecisionBuilder* MakeSelfDependentDimensionFinalizer(
|
|
const RoutingDimension* dimension);
|
|
|
|
private:
|
|
/// Local search move operator usable in routing.
|
|
enum RoutingLocalSearchOperator {
|
|
RELOCATE = 0,
|
|
RELOCATE_PAIR,
|
|
LIGHT_RELOCATE_PAIR,
|
|
RELOCATE_NEIGHBORS,
|
|
EXCHANGE,
|
|
EXCHANGE_PAIR,
|
|
CROSS,
|
|
CROSS_EXCHANGE,
|
|
TWO_OPT,
|
|
OR_OPT,
|
|
GLOBAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS,
|
|
LOCAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS,
|
|
GLOBAL_CHEAPEST_INSERTION_PATH_LNS,
|
|
LOCAL_CHEAPEST_INSERTION_PATH_LNS,
|
|
RELOCATE_PATH_GLOBAL_CHEAPEST_INSERTION_INSERT_UNPERFORMED,
|
|
GLOBAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS,
|
|
LOCAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS,
|
|
RELOCATE_EXPENSIVE_CHAIN,
|
|
LIN_KERNIGHAN,
|
|
TSP_OPT,
|
|
MAKE_ACTIVE,
|
|
RELOCATE_AND_MAKE_ACTIVE,
|
|
MAKE_ACTIVE_AND_RELOCATE,
|
|
MAKE_INACTIVE,
|
|
MAKE_CHAIN_INACTIVE,
|
|
SWAP_ACTIVE,
|
|
EXTENDED_SWAP_ACTIVE,
|
|
NODE_PAIR_SWAP,
|
|
PATH_LNS,
|
|
FULL_PATH_LNS,
|
|
TSP_LNS,
|
|
INACTIVE_LNS,
|
|
EXCHANGE_RELOCATE_PAIR,
|
|
RELOCATE_SUBTRIP,
|
|
EXCHANGE_SUBTRIP,
|
|
LOCAL_SEARCH_OPERATOR_COUNTER
|
|
};
|
|
|
|
/// Structure storing a value for a set of variable indices. Is used to store
|
|
/// data for index disjunctions (variable indices, max_cardinality and penalty
|
|
/// when unperformed).
|
|
template <typename T>
|
|
struct ValuedNodes {
|
|
std::vector<int64_t> indices;
|
|
T value;
|
|
};
|
|
struct DisjunctionValues {
|
|
int64_t penalty;
|
|
int64_t max_cardinality;
|
|
};
|
|
typedef ValuedNodes<DisjunctionValues> Disjunction;
|
|
|
|
/// Storage of a cost cache element corresponding to a cost arc ending at
|
|
/// node 'index' and on the cost class 'cost_class'.
|
|
struct CostCacheElement {
|
|
/// This is usually an int64_t, but using an int here decreases the RAM
|
|
/// usage, and should be fine since in practice we never have more than
|
|
/// 1<<31 vars. Note(user): on 2013-11, microbenchmarks on the arc costs
|
|
/// callbacks also showed a 2% speed-up thanks to using int rather than
|
|
/// int64_t.
|
|
int index;
|
|
CostClassIndex cost_class_index;
|
|
int64_t cost;
|
|
};
|
|
|
|
/// Internal struct used to store the lp/mp versions of the local and global
|
|
/// cumul optimizers for a given dimension.
|
|
template <class DimensionCumulOptimizer>
|
|
struct DimensionCumulOptimizers {
|
|
std::unique_ptr<DimensionCumulOptimizer> lp_optimizer;
|
|
std::unique_ptr<DimensionCumulOptimizer> mp_optimizer;
|
|
};
|
|
|
|
/// Internal methods.
|
|
void Initialize();
|
|
void AddNoCycleConstraintInternal();
|
|
bool AddDimensionWithCapacityInternal(
|
|
const std::vector<int>& evaluator_indices, int64_t slack_max,
|
|
std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
|
|
const std::string& name);
|
|
bool AddDimensionDependentDimensionWithVehicleCapacityInternal(
|
|
const std::vector<int>& pure_transits,
|
|
const std::vector<int>& dependent_transits,
|
|
const RoutingDimension* base_dimension, int64_t slack_max,
|
|
std::vector<int64_t> vehicle_capacities, bool fix_start_cumul_to_zero,
|
|
const std::string& name);
|
|
bool InitializeDimensionInternal(
|
|
const std::vector<int>& evaluator_indices,
|
|
const std::vector<int>& state_dependent_evaluator_indices,
|
|
int64_t slack_max, bool fix_start_cumul_to_zero,
|
|
RoutingDimension* dimension);
|
|
DimensionIndex GetDimensionIndex(const std::string& dimension_name) const;
|
|
|
|
/// Creates global and local cumul optimizers for the dimensions needing them,
|
|
/// and stores them in the corresponding [local|global]_dimension_optimizers_
|
|
/// vectors.
|
|
/// This function also computes and stores the "offsets" for these dimensions,
|
|
/// used in the local/global optimizers to simplify LP computations.
|
|
///
|
|
/// Note on the offsets computation:
|
|
/// The global/local cumul offsets are used by the respective optimizers to
|
|
/// have smaller numbers, and therefore better numerical behavior in the LP.
|
|
/// These offsets are used as a minimum value for the cumuls over the route
|
|
/// (or globally), i.e. a value we consider all cumuls to be greater or equal
|
|
/// to. When transits are all positive, the cumuls of every node on a route is
|
|
/// necessarily greater than the cumul of its start. Therefore, the local
|
|
/// offset for a vehicle can be set to the minimum of its start node's cumul,
|
|
/// and for the global optimizers, to the min start cumul over all vehicles.
|
|
/// However, to be able to distinguish between infeasible nodes (i.e. nodes
|
|
/// for which the cumul upper bound is less than the min cumul of the
|
|
/// vehicle's start), we set the offset to "min_start_cumul" - 1. By doing so,
|
|
/// all infeasible nodes described above will have bounds of [0, 0]. Example:
|
|
/// Start cumul bounds: [11, 20] --> offset = 11 - 1 = 10.
|
|
/// Two nodes with cumul bounds. Node1: [5, 10], Node2: [7, 20]
|
|
/// After applying the offset to the above windows, they become:
|
|
/// Vehicle: [1, 10]. Node1: [0, 0] (infeasible). Node2: [0, 10].
|
|
///
|
|
/// On the other hand, when transits on a route can be negative, no assumption
|
|
/// can be made on the cumuls of nodes wrt the start cumuls, and the offset is
|
|
/// therefore set to 0.
|
|
void StoreDimensionCumulOptimizers(const RoutingSearchParameters& parameters);
|
|
|
|
void ComputeCostClasses(const RoutingSearchParameters& parameters);
|
|
void ComputeVehicleClasses();
|
|
/// The following method initializes the vehicle_type_container_:
|
|
/// - Computes the vehicle types of vehicles and stores it in
|
|
/// type_index_of_vehicle.
|
|
/// - The vehicle classes corresponding to each vehicle type index are stored
|
|
/// and sorted by fixed cost in sorted_vehicle_classes_per_type.
|
|
/// - The vehicles for each vehicle class are stored in
|
|
/// vehicles_per_vehicle_class.
|
|
void ComputeVehicleTypes();
|
|
/// This method scans the visit types and sets up the following members:
|
|
/// - single_nodes_of_type_[type] contains indices of nodes of visit type
|
|
/// "type" which are not part of any pickup/delivery pair.
|
|
/// - pair_indices_of_type_[type] is the set of "pair_index" such that
|
|
/// pickup_delivery_pairs_[pair_index] has at least one pickup or delivery
|
|
/// with visit type "type".
|
|
/// - topologically_sorted_visit_types_ contains the visit types in
|
|
/// topological order based on required-->dependent arcs from the
|
|
/// visit type requirements.
|
|
void FinalizeVisitTypes();
|
|
// Called by FinalizeVisitTypes() to setup topologically_sorted_visit_types_.
|
|
void TopologicallySortVisitTypes();
|
|
int64_t GetArcCostForClassInternal(int64_t from_index, int64_t to_index,
|
|
CostClassIndex cost_class_index) const;
|
|
void AppendHomogeneousArcCosts(const RoutingSearchParameters& parameters,
|
|
int node_index,
|
|
std::vector<IntVar*>* cost_elements);
|
|
void AppendArcCosts(const RoutingSearchParameters& parameters, int node_index,
|
|
std::vector<IntVar*>* cost_elements);
|
|
Assignment* DoRestoreAssignment();
|
|
static const CostClassIndex kCostClassIndexOfZeroCost;
|
|
int64_t SafeGetCostClassInt64OfVehicle(int64_t vehicle) const {
|
|
DCHECK_LT(0, vehicles_);
|
|
return (vehicle >= 0 ? GetCostClassIndexOfVehicle(vehicle)
|
|
: kCostClassIndexOfZeroCost)
|
|
.value();
|
|
}
|
|
int64_t GetDimensionTransitCostSum(int64_t i, int64_t j,
|
|
const CostClass& cost_class) const;
|
|
/// Returns nullptr if no penalty cost, otherwise returns penalty variable.
|
|
IntVar* CreateDisjunction(DisjunctionIndex disjunction);
|
|
/// Sets up pickup and delivery sets.
|
|
void AddPickupAndDeliverySetsInternal(const std::vector<int64_t>& pickups,
|
|
const std::vector<int64_t>& deliveries);
|
|
/// Returns the cost variable related to the soft same vehicle constraint of
|
|
/// index 'vehicle_index'.
|
|
IntVar* CreateSameVehicleCost(int vehicle_index);
|
|
/// Returns the first active variable index in 'indices' starting from index
|
|
/// + 1.
|
|
int FindNextActive(int index, const std::vector<int64_t>& indices) const;
|
|
|
|
/// Checks that all nodes on the route starting at start_index (using the
|
|
/// solution stored in assignment) can be visited by the given vehicle.
|
|
bool RouteCanBeUsedByVehicle(const Assignment& assignment, int start_index,
|
|
int vehicle) const;
|
|
/// Replaces the route of unused_vehicle with the route of active_vehicle in
|
|
/// compact_assignment. Expects that unused_vehicle is a vehicle with an empty
|
|
/// route and that the route of active_vehicle is non-empty. Also expects that
|
|
/// 'assignment' contains the original assignment, from which
|
|
/// compact_assignment was created.
|
|
/// Returns true if the vehicles were successfully swapped; otherwise, returns
|
|
/// false.
|
|
bool ReplaceUnusedVehicle(int unused_vehicle, int active_vehicle,
|
|
Assignment* compact_assignment) const;
|
|
|
|
void QuietCloseModel();
|
|
void QuietCloseModelWithParameters(
|
|
const RoutingSearchParameters& parameters) {
|
|
if (!closed_) {
|
|
CloseModelWithParameters(parameters);
|
|
}
|
|
}
|
|
|
|
/// Solve matching problem with min-cost flow and store result in assignment.
|
|
bool SolveMatchingModel(Assignment* assignment,
|
|
const RoutingSearchParameters& parameters);
|
|
#ifndef SWIG
|
|
/// Append an assignment to a vector of assignments if it is feasible.
|
|
bool AppendAssignmentIfFeasible(
|
|
const Assignment& assignment,
|
|
std::vector<std::unique_ptr<Assignment>>* assignments);
|
|
#endif
|
|
/// Log a solution.
|
|
void LogSolution(const RoutingSearchParameters& parameters,
|
|
const std::string& description, int64_t solution_cost,
|
|
int64_t start_time_ms);
|
|
/// See CompactAssignment. Checks the final solution if
|
|
/// check_compact_assignment is true.
|
|
Assignment* CompactAssignmentInternal(const Assignment& assignment,
|
|
bool check_compact_assignment) const;
|
|
/// Checks that the current search parameters are valid for the current
|
|
/// model's specific settings. This assumes that FindErrorInSearchParameters()
|
|
/// from
|
|
/// ./routing_flags.h caught no error.
|
|
std::string FindErrorInSearchParametersForModel(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
/// Sets up search objects, such as decision builders and monitors.
|
|
void SetupSearch(const RoutingSearchParameters& search_parameters);
|
|
/// Set of auxiliary methods used to setup the search.
|
|
// TODO(user): Document each auxiliary method.
|
|
Assignment* GetOrCreateAssignment();
|
|
Assignment* GetOrCreateTmpAssignment();
|
|
RegularLimit* GetOrCreateLimit();
|
|
RegularLimit* GetOrCreateLocalSearchLimit();
|
|
RegularLimit* GetOrCreateLargeNeighborhoodSearchLimit();
|
|
RegularLimit* GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit();
|
|
LocalSearchOperator* CreateInsertionOperator();
|
|
LocalSearchOperator* CreateMakeInactiveOperator();
|
|
template <class T>
|
|
LocalSearchOperator* CreateCPOperator(const T& operator_factory) {
|
|
return operator_factory(solver_.get(), nexts_,
|
|
CostsAreHomogeneousAcrossVehicles()
|
|
? std::vector<IntVar*>()
|
|
: vehicle_vars_,
|
|
vehicle_start_class_callback_);
|
|
}
|
|
template <class T>
|
|
LocalSearchOperator* CreateCPOperator() {
|
|
return CreateCPOperator(MakeLocalSearchOperator<T>);
|
|
}
|
|
template <class T, class Arg>
|
|
LocalSearchOperator* CreateOperator(const Arg& arg) {
|
|
return solver_->RevAlloc(new T(nexts_,
|
|
CostsAreHomogeneousAcrossVehicles()
|
|
? std::vector<IntVar*>()
|
|
: vehicle_vars_,
|
|
vehicle_start_class_callback_, arg));
|
|
}
|
|
template <class T>
|
|
LocalSearchOperator* CreatePairOperator() {
|
|
return CreateOperator<T>(pickup_delivery_pairs_);
|
|
}
|
|
void CreateNeighborhoodOperators(const RoutingSearchParameters& parameters);
|
|
LocalSearchOperator* ConcatenateOperators(
|
|
const RoutingSearchParameters& search_parameters,
|
|
const std::vector<LocalSearchOperator*>& operators) const;
|
|
LocalSearchOperator* GetNeighborhoodOperators(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
|
|
struct FilterOptions {
|
|
bool filter_objective;
|
|
bool filter_with_cp_solver;
|
|
|
|
bool operator==(const FilterOptions& other) const {
|
|
return other.filter_objective == filter_objective &&
|
|
other.filter_with_cp_solver == filter_with_cp_solver;
|
|
}
|
|
template <typename H>
|
|
friend H AbslHashValue(H h, const FilterOptions& options) {
|
|
return H::combine(std::move(h), options.filter_objective,
|
|
options.filter_with_cp_solver);
|
|
}
|
|
};
|
|
std::vector<LocalSearchFilterManager::FilterEvent> CreateLocalSearchFilters(
|
|
const RoutingSearchParameters& parameters, const FilterOptions& options);
|
|
LocalSearchFilterManager* GetOrCreateLocalSearchFilterManager(
|
|
const RoutingSearchParameters& parameters, const FilterOptions& options);
|
|
DecisionBuilder* CreateSolutionFinalizer(
|
|
const RoutingSearchParameters& parameters, SearchLimit* lns_limit);
|
|
DecisionBuilder* CreateFinalizerForMinimizedAndMaximizedVariables();
|
|
void CreateFirstSolutionDecisionBuilders(
|
|
const RoutingSearchParameters& search_parameters);
|
|
DecisionBuilder* GetFirstSolutionDecisionBuilder(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
IntVarFilteredDecisionBuilder* GetFilteredFirstSolutionDecisionBuilderOrNull(
|
|
const RoutingSearchParameters& parameters) const;
|
|
LocalSearchPhaseParameters* CreateLocalSearchParameters(
|
|
const RoutingSearchParameters& search_parameters);
|
|
DecisionBuilder* CreateLocalSearchDecisionBuilder(
|
|
const RoutingSearchParameters& search_parameters);
|
|
void SetupDecisionBuilders(const RoutingSearchParameters& search_parameters);
|
|
void SetupMetaheuristics(const RoutingSearchParameters& search_parameters);
|
|
void SetupAssignmentCollector(
|
|
const RoutingSearchParameters& search_parameters);
|
|
void SetupTrace(const RoutingSearchParameters& search_parameters);
|
|
void SetupImprovementLimit(const RoutingSearchParameters& search_parameters);
|
|
void SetupSearchMonitors(const RoutingSearchParameters& search_parameters);
|
|
bool UsesLightPropagation(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
GetTabuVarsCallback tabu_var_callback_;
|
|
|
|
// Detects implicit pickup delivery pairs. These pairs are
|
|
// non-pickup/delivery pairs for which there exists a unary dimension such
|
|
// that the demand d of the implicit pickup is positive and the demand of the
|
|
// implicit delivery is equal to -d.
|
|
void DetectImplicitPickupAndDeliveries();
|
|
|
|
int GetVehicleStartClass(int64_t 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);
|
|
}
|
|
|
|
/// Returns the internal global/local optimizer index for the given dimension
|
|
/// if any, and -1 otherwise.
|
|
int GetGlobalCumulOptimizerIndex(const RoutingDimension& dimension) const;
|
|
int GetLocalCumulOptimizerIndex(const RoutingDimension& dimension) const;
|
|
|
|
/// Model
|
|
std::unique_ptr<Solver> solver_;
|
|
int nodes_;
|
|
int vehicles_;
|
|
int max_active_vehicles_;
|
|
Constraint* no_cycle_constraint_ = nullptr;
|
|
/// Decision variables: indexed by int64_t var index.
|
|
std::vector<IntVar*> nexts_;
|
|
std::vector<IntVar*> vehicle_vars_;
|
|
std::vector<IntVar*> active_;
|
|
/// Resource variables, indexed first by resource group index and then by
|
|
/// vehicle index. A resource variable can have a negative value of -1, iff
|
|
/// the corresponding vehicle doesn't require a resource from this resource
|
|
/// group, OR if the vehicle is unused (i.e. no visits on its route and
|
|
/// vehicle_used_when_empty_[v] is false).
|
|
// clang-format off
|
|
std::vector<std::vector<IntVar*> > resource_vars_;
|
|
// clang-format on
|
|
// The following vectors are indexed by vehicle index.
|
|
std::vector<IntVar*> vehicle_active_;
|
|
std::vector<IntVar*> vehicle_route_considered_;
|
|
/// is_bound_to_end_[i] will be true iff the path starting at var #i is fully
|
|
/// bound and reaches the end of a route, i.e. either:
|
|
/// - 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_;
|
|
mutable RevSwitch is_bound_to_end_ct_added_;
|
|
/// Dimensions
|
|
absl::flat_hash_map<std::string, DimensionIndex> dimension_name_to_index_;
|
|
absl::StrongVector<DimensionIndex, RoutingDimension*> dimensions_;
|
|
/// Resource Groups.
|
|
/// If resource_groups_ is not empty, then for each group of resources, each
|
|
/// (used) vehicle must be assigned to exactly 1 resource, and each resource
|
|
/// can in turn be assigned to at most 1 vehicle.
|
|
// clang-format off
|
|
std::vector<std::unique_ptr<ResourceGroup> > resource_groups_;
|
|
/// Stores the set of resource groups related to each dimension.
|
|
absl::StrongVector<DimensionIndex, std::vector<int> >
|
|
dimension_resource_group_indices_;
|
|
|
|
/// TODO(user): Define a new Dimension[Global|Local]OptimizerIndex type
|
|
/// and use it to define ITIVectors and for the dimension to optimizer index
|
|
/// mappings below.
|
|
std::vector<DimensionCumulOptimizers<GlobalDimensionCumulOptimizer> >
|
|
global_dimension_optimizers_;
|
|
absl::StrongVector<DimensionIndex, int> global_optimizer_index_;
|
|
std::vector<DimensionCumulOptimizers<LocalDimensionCumulOptimizer> >
|
|
local_dimension_optimizers_;
|
|
absl::StrongVector<DimensionIndex, int> local_optimizer_index_;
|
|
// clang-format on
|
|
std::string primary_constrained_dimension_;
|
|
/// Costs
|
|
IntVar* cost_ = nullptr;
|
|
std::vector<int> vehicle_to_transit_cost_;
|
|
std::vector<int64_t> fixed_cost_of_vehicle_;
|
|
std::vector<CostClassIndex> cost_class_index_of_vehicle_;
|
|
bool has_vehicle_with_zero_cost_class_;
|
|
std::vector<int64_t> linear_cost_factor_of_vehicle_;
|
|
std::vector<int64_t> quadratic_cost_factor_of_vehicle_;
|
|
bool vehicle_amortized_cost_factors_set_;
|
|
/// vehicle_used_when_empty_[vehicle] determines if "vehicle" should be
|
|
/// taken into account for costs (arc costs, span costs, etc.) and constraints
|
|
/// (eg. resources) even when the route of the vehicle is empty (i.e. goes
|
|
/// straight from its start to its end).
|
|
///
|
|
/// NOTE1: A vehicle's fixed cost is added iff the vehicle serves nodes on its
|
|
/// route, regardless of this variable's value.
|
|
///
|
|
/// NOTE2: The default value for this boolean is 'false' for all vehicles,
|
|
/// i.e. by default empty routes will not contribute to the cost nor be
|
|
/// considered for constraints.
|
|
std::vector<bool> vehicle_used_when_empty_;
|
|
#ifndef SWIG
|
|
absl::StrongVector<CostClassIndex, CostClass> cost_classes_;
|
|
#endif // SWIG
|
|
bool costs_are_homogeneous_across_vehicles_;
|
|
bool cache_callbacks_;
|
|
mutable std::vector<CostCacheElement> cost_cache_; /// Index by source index.
|
|
std::vector<VehicleClassIndex> vehicle_class_index_of_vehicle_;
|
|
#ifndef SWIG
|
|
absl::StrongVector<VehicleClassIndex, VehicleClass> vehicle_classes_;
|
|
#endif // SWIG
|
|
VehicleTypeContainer vehicle_type_container_;
|
|
std::function<int(int64_t)> vehicle_start_class_callback_;
|
|
/// Disjunctions
|
|
absl::StrongVector<DisjunctionIndex, Disjunction> disjunctions_;
|
|
// clang-format off
|
|
std::vector<std::vector<DisjunctionIndex> > index_to_disjunctions_;
|
|
/// Same vehicle costs
|
|
std::vector<ValuedNodes<int64_t> > same_vehicle_costs_;
|
|
/// Allowed vehicles
|
|
#ifndef SWIG
|
|
std::vector<absl::flat_hash_set<int>> allowed_vehicles_;
|
|
#endif // SWIG
|
|
/// Pickup and delivery
|
|
IndexPairs pickup_delivery_pairs_;
|
|
IndexPairs implicit_pickup_delivery_pairs_without_alternatives_;
|
|
std::vector<std::pair<DisjunctionIndex, DisjunctionIndex> >
|
|
pickup_delivery_disjunctions_;
|
|
// If node_index is a pickup, index_to_pickup_index_pairs_[node_index] is the
|
|
// vector of pairs {pair_index, pickup_index} such that
|
|
// (pickup_delivery_pairs_[pair_index].first)[pickup_index] == node_index
|
|
std::vector<std::vector<std::pair<int, int> > > index_to_pickup_index_pairs_;
|
|
// Same as above for deliveries.
|
|
std::vector<std::vector<std::pair<int, int> > >
|
|
index_to_delivery_index_pairs_;
|
|
// clang-format on
|
|
std::vector<PickupAndDeliveryPolicy> vehicle_pickup_delivery_policy_;
|
|
// 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_;
|
|
// Node visit types
|
|
// Variable index to visit type index.
|
|
std::vector<int> index_to_visit_type_;
|
|
// Variable index to VisitTypePolicy.
|
|
std::vector<VisitTypePolicy> index_to_type_policy_;
|
|
// clang-format off
|
|
std::vector<std::vector<int> > single_nodes_of_type_;
|
|
std::vector<std::vector<int> > pair_indices_of_type_;
|
|
|
|
std::vector<absl::flat_hash_set<int> >
|
|
hard_incompatible_types_per_type_index_;
|
|
bool has_hard_type_incompatibilities_;
|
|
std::vector<absl::flat_hash_set<int> >
|
|
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> > >
|
|
required_type_alternatives_when_adding_type_index_;
|
|
std::vector<std::vector<absl::flat_hash_set<int> > >
|
|
required_type_alternatives_when_removing_type_index_;
|
|
bool has_temporal_type_requirements_;
|
|
absl::flat_hash_map</*type*/int, absl::flat_hash_set<VisitTypePolicy> >
|
|
trivially_infeasible_visit_types_to_policies_;
|
|
|
|
// Visit types sorted topologically based on required-->dependent requirement
|
|
// arcs between the types (if the requirement/dependency graph is acyclic).
|
|
// Visit types of the same topological level are sorted in each sub-vector
|
|
// by decreasing requirement "tightness", computed as the pair of the two
|
|
// following criteria:
|
|
//
|
|
// 1) How highly *dependent* this type is, determined by
|
|
// (total number of required alternative sets for that type)
|
|
// / (average number of types in the required alternative sets)
|
|
// 2) How highly *required* this type t is, computed as
|
|
// SUM_{S required set containing t} ( 1 / |S| ),
|
|
// i.e. the sum of reverse number of elements of all required sets
|
|
// containing the type t.
|
|
//
|
|
// The higher these two numbers, the tighter the type is wrt requirements.
|
|
std::vector<std::vector<int> > topologically_sorted_visit_types_;
|
|
// clang-format on
|
|
int num_visit_types_;
|
|
// Two indices are equivalent if they correspond to the same node (as given
|
|
// to the constructors taking a RoutingIndexManager).
|
|
std::vector<int> index_to_equivalence_class_;
|
|
const PathsMetadata paths_metadata_;
|
|
// TODO(user): b/62478706 Once the port is done, this shouldn't be needed
|
|
// anymore.
|
|
RoutingIndexManager manager_;
|
|
int start_end_count_;
|
|
// Model status
|
|
bool closed_ = false;
|
|
Status status_ = ROUTING_NOT_SOLVED;
|
|
bool enable_deep_serialization_ = true;
|
|
|
|
// Search data
|
|
std::vector<DecisionBuilder*> first_solution_decision_builders_;
|
|
std::vector<IntVarFilteredDecisionBuilder*>
|
|
first_solution_filtered_decision_builders_;
|
|
Solver::IndexEvaluator2 first_solution_evaluator_;
|
|
FirstSolutionStrategy::Value automatic_first_solution_strategy_ =
|
|
FirstSolutionStrategy::UNSET;
|
|
std::vector<LocalSearchOperator*> local_search_operators_;
|
|
std::vector<SearchMonitor*> monitors_;
|
|
SolutionCollector* collect_assignments_ = nullptr;
|
|
SolutionCollector* collect_one_assignment_ = nullptr;
|
|
SolutionCollector* optimized_dimensions_assignment_collector_ = nullptr;
|
|
DecisionBuilder* solve_db_ = nullptr;
|
|
DecisionBuilder* improve_db_ = nullptr;
|
|
DecisionBuilder* restore_assignment_ = nullptr;
|
|
DecisionBuilder* restore_tmp_assignment_ = nullptr;
|
|
Assignment* assignment_ = nullptr;
|
|
Assignment* preassignment_ = nullptr;
|
|
Assignment* tmp_assignment_ = nullptr;
|
|
std::vector<IntVar*> extra_vars_;
|
|
std::vector<IntervalVar*> extra_intervals_;
|
|
std::vector<LocalSearchOperator*> extra_operators_;
|
|
absl::flat_hash_map<FilterOptions, LocalSearchFilterManager*>
|
|
local_search_filter_managers_;
|
|
std::vector<LocalSearchFilterManager::FilterEvent> extra_filters_;
|
|
#ifndef SWIG
|
|
struct VarTarget {
|
|
VarTarget(IntVar* v, int64_t t) : var(v), target(t) {}
|
|
|
|
IntVar* var;
|
|
int64_t target;
|
|
};
|
|
std::vector<std::pair<VarTarget, int64_t>>
|
|
weighted_finalizer_variable_targets_;
|
|
std::vector<VarTarget> finalizer_variable_targets_;
|
|
absl::flat_hash_map<IntVar*, int> weighted_finalizer_variable_index_;
|
|
absl::flat_hash_set<IntVar*> finalizer_variable_target_set_;
|
|
std::unique_ptr<SweepArranger> sweep_arranger_;
|
|
#endif
|
|
|
|
RegularLimit* limit_ = nullptr;
|
|
RegularLimit* ls_limit_ = nullptr;
|
|
RegularLimit* lns_limit_ = nullptr;
|
|
RegularLimit* first_solution_lns_limit_ = nullptr;
|
|
|
|
typedef std::pair<int64_t, int64_t> CacheKey;
|
|
typedef absl::flat_hash_map<CacheKey, int64_t> TransitCallbackCache;
|
|
typedef absl::flat_hash_map<CacheKey, StateDependentTransit>
|
|
StateDependentTransitCallbackCache;
|
|
|
|
std::vector<TransitCallback1> unary_transit_evaluators_;
|
|
std::vector<TransitCallback2> transit_evaluators_;
|
|
// The following vector stores a boolean per transit_evaluator_, indicating
|
|
// whether the transits are all positive.
|
|
// is_transit_evaluator_positive_ will be set to true only when registering a
|
|
// callback via RegisterPositiveTransitCallback(), and to false otherwise.
|
|
// The actual positivity of the transit values will only be checked in debug
|
|
// mode, when calling RegisterPositiveTransitCallback().
|
|
// Therefore, RegisterPositiveTransitCallback() should only be called when the
|
|
// transits are known to be positive, as the positivity of a callback will
|
|
// allow some improvements in the solver, but will entail in errors if the
|
|
// transits are falsely assumed positive.
|
|
std::vector<bool> is_transit_evaluator_positive_;
|
|
std::vector<VariableIndexEvaluator2> state_dependent_transit_evaluators_;
|
|
std::vector<std::unique_ptr<StateDependentTransitCallbackCache>>
|
|
state_dependent_transit_evaluators_cache_;
|
|
|
|
friend class RoutingDimension;
|
|
friend class RoutingModelInspector;
|
|
friend class ResourceGroup::Resource;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(RoutingModel);
|
|
};
|
|
|
|
/// Routing model visitor.
|
|
class RoutingModelVisitor : public BaseObject {
|
|
public:
|
|
/// Constraint types.
|
|
static const char kLightElement[];
|
|
static const char kLightElement2[];
|
|
static const char kRemoveValues[];
|
|
};
|
|
|
|
#if !defined(SWIG)
|
|
/// This class acts like a CP propagator: it takes a set of tasks given by
|
|
/// their start/duration/end features, and reduces the range of possible values.
|
|
class DisjunctivePropagator {
|
|
public:
|
|
/// A structure to hold tasks described by their features.
|
|
/// The first num_chain_tasks are considered linked by a chain of precedences,
|
|
/// i.e. if i < j < num_chain_tasks, then end(i) <= start(j).
|
|
/// This occurs frequently in routing, and can be leveraged by
|
|
/// some variants of classic propagators.
|
|
struct Tasks {
|
|
int num_chain_tasks = 0;
|
|
std::vector<int64_t> start_min;
|
|
std::vector<int64_t> start_max;
|
|
std::vector<int64_t> duration_min;
|
|
std::vector<int64_t> duration_max;
|
|
std::vector<int64_t> end_min;
|
|
std::vector<int64_t> end_max;
|
|
std::vector<bool> is_preemptible;
|
|
std::vector<const SortedDisjointIntervalList*> forbidden_intervals;
|
|
std::vector<std::pair<int64_t, int64_t>> distance_duration;
|
|
int64_t span_min = 0;
|
|
int64_t span_max = kint64max;
|
|
|
|
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();
|
|
span_min = 0;
|
|
span_max = kint64max;
|
|
num_chain_tasks = 0;
|
|
}
|
|
};
|
|
|
|
/// Computes new bounds for all tasks, returns false if infeasible.
|
|
/// This does not compute a fixed point, so recalling it may filter more.
|
|
bool Propagate(Tasks* tasks);
|
|
|
|
/// Propagates the deductions from the chain of precedences, if there is one.
|
|
bool Precedences(Tasks* tasks);
|
|
/// Transforms the problem with a time symmetry centered in 0. Returns true
|
|
/// for convenience.
|
|
bool MirrorTasks(Tasks* tasks);
|
|
/// Does edge-finding deductions on all tasks.
|
|
bool EdgeFinding(Tasks* tasks);
|
|
/// Does detectable precedences deductions on tasks in the chain precedence,
|
|
/// taking the time windows of nonchain tasks into account.
|
|
bool DetectablePrecedencesWithChain(Tasks* tasks);
|
|
/// Tasks might have holes in their domain, this enforces such holes.
|
|
bool ForbiddenIntervals(Tasks* tasks);
|
|
/// Propagates distance_duration constraints, if any.
|
|
bool DistanceDuration(Tasks* tasks);
|
|
/// Propagates a lower bound of the chain span,
|
|
/// end[num_chain_tasks] - start[0], to span_min.
|
|
bool ChainSpanMin(Tasks* tasks);
|
|
/// Computes a lower bound of the span of the chain, taking into account only
|
|
/// the first nonchain task.
|
|
/// For more accurate results, this should be called after Precedences(),
|
|
/// otherwise the lower bound might be lower than feasible.
|
|
bool ChainSpanMinDynamic(Tasks* tasks);
|
|
|
|
private:
|
|
/// The main algorithm uses Vilim's theta tree data structure.
|
|
/// See Petr Vilim's PhD thesis "Global Constraints in Scheduling".
|
|
sat::ThetaLambdaTree<int64_t> theta_lambda_tree_;
|
|
/// Mappings between events and tasks.
|
|
std::vector<int> tasks_by_start_min_;
|
|
std::vector<int> tasks_by_end_max_;
|
|
std::vector<int> event_of_task_;
|
|
std::vector<int> nonchain_tasks_by_start_max_;
|
|
/// Maps chain elements to the sum of chain task durations before them.
|
|
std::vector<int64_t> total_duration_before_;
|
|
};
|
|
|
|
struct TravelBounds {
|
|
std::vector<int64_t> min_travels;
|
|
std::vector<int64_t> max_travels;
|
|
std::vector<int64_t> pre_travels;
|
|
std::vector<int64_t> post_travels;
|
|
};
|
|
|
|
void AppendTasksFromPath(const std::vector<int64_t>& path,
|
|
const TravelBounds& travel_bounds,
|
|
const RoutingDimension& dimension,
|
|
DisjunctivePropagator::Tasks* tasks);
|
|
void AppendTasksFromIntervals(const std::vector<IntervalVar*>& intervals,
|
|
DisjunctivePropagator::Tasks* tasks);
|
|
void FillPathEvaluation(const std::vector<int64_t>& path,
|
|
const RoutingModel::TransitCallback2& evaluator,
|
|
std::vector<int64_t>* values);
|
|
void FillTravelBoundsOfVehicle(int vehicle, const std::vector<int64_t>& path,
|
|
const RoutingDimension& dimension,
|
|
TravelBounds* travel_bounds);
|
|
#endif // !defined(SWIG)
|
|
|
|
/// GlobalVehicleBreaksConstraint ensures breaks constraints are enforced on
|
|
/// all vehicles in the dimension passed to its constructor.
|
|
/// It is intended to be used for dimensions representing time.
|
|
/// A break constraint ensures break intervals fit on the route of a vehicle.
|
|
/// For a given vehicle, it forces break intervals to be disjoint from visit
|
|
/// intervals, where visit intervals start at CumulVar(node) and last for
|
|
/// node_visit_transit[node]. Moreover, it ensures that there is enough time
|
|
/// 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).
|
|
class GlobalVehicleBreaksConstraint : public Constraint {
|
|
public:
|
|
explicit GlobalVehicleBreaksConstraint(const RoutingDimension* dimension);
|
|
std::string DebugString() const override {
|
|
return "GlobalVehicleBreaksConstraint";
|
|
}
|
|
|
|
void Post() override;
|
|
void InitialPropagate() override;
|
|
|
|
private:
|
|
void PropagateNode(int node);
|
|
void PropagateVehicle(int vehicle);
|
|
|
|
const RoutingModel* model_;
|
|
const RoutingDimension* const dimension_;
|
|
std::vector<Demon*> vehicle_demons_;
|
|
std::vector<int64_t> 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_t>& 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. 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_t before_start, int64_t after_start)
|
|
: start_(start),
|
|
before_start_(before_start),
|
|
after_start_(after_start) {}
|
|
explicit TaskTranslator(IntervalVar* interval) : interval_(interval) {}
|
|
TaskTranslator() {}
|
|
|
|
void SetStartMin(int64_t value) {
|
|
if (start_ != nullptr) {
|
|
start_->SetMin(CapAdd(before_start_, value));
|
|
} else if (interval_ != nullptr) {
|
|
interval_->SetStartMin(value);
|
|
}
|
|
}
|
|
void SetStartMax(int64_t value) {
|
|
if (start_ != nullptr) {
|
|
start_->SetMax(CapAdd(before_start_, value));
|
|
} else if (interval_ != nullptr) {
|
|
interval_->SetStartMax(value);
|
|
}
|
|
}
|
|
void SetDurationMin(int64_t value) {
|
|
if (interval_ != nullptr) {
|
|
interval_->SetDurationMin(value);
|
|
}
|
|
}
|
|
void SetEndMin(int64_t value) {
|
|
if (start_ != nullptr) {
|
|
start_->SetMin(CapSub(value, after_start_));
|
|
} else if (interval_ != nullptr) {
|
|
interval_->SetEndMin(value);
|
|
}
|
|
}
|
|
void SetEndMax(int64_t value) {
|
|
if (start_ != nullptr) {
|
|
start_->SetMax(CapSub(value, after_start_));
|
|
} else if (interval_ != nullptr) {
|
|
interval_->SetEndMax(value);
|
|
}
|
|
}
|
|
|
|
private:
|
|
IntVar* start_ = nullptr;
|
|
int64_t before_start_;
|
|
int64_t after_start_;
|
|
IntervalVar* interval_ = nullptr;
|
|
};
|
|
|
|
/// Route and interval variables are normalized to the following values.
|
|
std::vector<TaskTranslator> task_translators_;
|
|
|
|
/// This is used to restrict bounds of tasks.
|
|
DisjunctivePropagator disjunctive_propagator_;
|
|
DisjunctivePropagator::Tasks tasks_;
|
|
|
|
/// Used to help filling tasks_ at each propagation.
|
|
TravelBounds travel_bounds_;
|
|
};
|
|
|
|
class TypeRegulationsChecker {
|
|
public:
|
|
explicit TypeRegulationsChecker(const RoutingModel& model);
|
|
virtual ~TypeRegulationsChecker() {}
|
|
|
|
bool CheckVehicle(int vehicle,
|
|
const std::function<int64_t(int64_t)>& next_accessor);
|
|
|
|
protected:
|
|
#ifndef SWIG
|
|
using VisitTypePolicy = RoutingModel::VisitTypePolicy;
|
|
#endif // SWIG
|
|
|
|
struct TypePolicyOccurrence {
|
|
/// Number of TYPE_ADDED_TO_VEHICLE and
|
|
/// TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED node type policies seen on the
|
|
/// route.
|
|
int num_type_added_to_vehicle = 0;
|
|
/// Number of ADDED_TYPE_REMOVED_FROM_VEHICLE (effectively removing a type
|
|
/// from the route) and TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED node type
|
|
/// policies seen on the route.
|
|
/// This number is always <= num_type_added_to_vehicle, as a type is only
|
|
/// actually removed if it was on the route before.
|
|
int num_type_removed_from_vehicle = 0;
|
|
/// Position of the last node of policy TYPE_ON_VEHICLE_UP_TO_VISIT visited
|
|
/// on the route.
|
|
/// If positive, the type is considered on the vehicle from the start of the
|
|
/// route until this position.
|
|
int position_of_last_type_on_vehicle_up_to_visit = -1;
|
|
};
|
|
|
|
/// Returns true iff any occurrence of the given type was seen on the route,
|
|
/// i.e. iff the added count for this type is positive, or if a node of this
|
|
/// type and policy TYPE_ON_VEHICLE_UP_TO_VISIT is visited on the route (see
|
|
/// TypePolicyOccurrence.last_type_on_vehicle_up_to_visit).
|
|
bool TypeOccursOnRoute(int type) const;
|
|
/// Returns true iff there's at least one instance of the given type on the
|
|
/// route when scanning the route at the given position 'pos'.
|
|
/// This is the case iff we have at least one added but non-removed instance
|
|
/// of the type, or if
|
|
/// occurrences_of_type_[type].last_type_on_vehicle_up_to_visit is greater
|
|
/// than 'pos'.
|
|
bool TypeCurrentlyOnRoute(int type, int pos) const;
|
|
|
|
void InitializeCheck(int vehicle,
|
|
const std::function<int64_t(int64_t)>& next_accessor);
|
|
virtual void OnInitializeCheck() {}
|
|
virtual bool HasRegulationsToCheck() const = 0;
|
|
virtual bool CheckTypeRegulations(int type, VisitTypePolicy policy,
|
|
int pos) = 0;
|
|
virtual bool FinalizeCheck() const { return true; }
|
|
|
|
const RoutingModel& model_;
|
|
|
|
private:
|
|
std::vector<TypePolicyOccurrence> occurrences_of_type_;
|
|
std::vector<int64_t> current_route_visits_;
|
|
};
|
|
|
|
/// Checker for type incompatibilities.
|
|
class TypeIncompatibilityChecker : public TypeRegulationsChecker {
|
|
public:
|
|
TypeIncompatibilityChecker(const RoutingModel& model,
|
|
bool check_hard_incompatibilities);
|
|
~TypeIncompatibilityChecker() override {}
|
|
|
|
private:
|
|
bool HasRegulationsToCheck() const override;
|
|
bool CheckTypeRegulations(int type, VisitTypePolicy policy, int pos) 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.
|
|
bool check_hard_incompatibilities_;
|
|
};
|
|
|
|
/// Checker for type requirements.
|
|
class TypeRequirementChecker : public TypeRegulationsChecker {
|
|
public:
|
|
explicit TypeRequirementChecker(const RoutingModel& model)
|
|
: TypeRegulationsChecker(model) {}
|
|
~TypeRequirementChecker() override {}
|
|
|
|
private:
|
|
bool HasRegulationsToCheck() const override;
|
|
void OnInitializeCheck() override {
|
|
types_with_same_vehicle_requirements_on_route_.clear();
|
|
}
|
|
// clang-format off
|
|
/// Verifies that for each set in required_type_alternatives, at least one of
|
|
/// the required types is on the route at position 'pos'.
|
|
bool CheckRequiredTypesCurrentlyOnRoute(
|
|
const std::vector<absl::flat_hash_set<int> >& required_type_alternatives,
|
|
int pos);
|
|
// clang-format on
|
|
bool CheckTypeRegulations(int type, VisitTypePolicy policy, int pos) 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
|
|
/// between types are respected.
|
|
///
|
|
/// It verifies both "hard" and "temporal" incompatibilities.
|
|
/// Two nodes with hard incompatible types cannot be served by the same vehicle
|
|
/// at all, while with a temporal incompatibility they can't be on the same
|
|
/// route at the same time.
|
|
/// The VisitTypePolicy of a node determines how visiting it impacts the type
|
|
/// count on the route.
|
|
///
|
|
/// For example, for
|
|
/// - three temporally incompatible types T1 T2 and T3
|
|
/// - 2 pairs of nodes a1/r1 and a2/r2 of type T1 and T2 respectively, with
|
|
/// - a1 and a2 of VisitTypePolicy TYPE_ADDED_TO_VEHICLE
|
|
/// - r1 and r2 of policy ADDED_TYPE_REMOVED_FROM_VEHICLE
|
|
/// - 3 nodes A, UV and AR of type T3, respectively with type policies
|
|
/// TYPE_ADDED_TO_VEHICLE, TYPE_ON_VEHICLE_UP_TO_VISIT and
|
|
/// TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED
|
|
/// the configurations
|
|
/// UV --> a1 --> r1 --> a2 --> r2, a1 --> r1 --> a2 --> r2 --> A and
|
|
/// a1 --> r1 --> AR --> a2 --> r2 are acceptable, whereas the configurations
|
|
/// a1 --> a2 --> r1 --> ..., or A --> a1 --> r1 --> ..., or
|
|
/// a1 --> r1 --> UV --> ... are not feasible.
|
|
///
|
|
/// It also verifies same-vehicle and temporal type requirements.
|
|
/// A node of type T_d with a same-vehicle requirement for type T_r needs to be
|
|
/// served by the same vehicle as a node of type T_r.
|
|
/// Temporal requirements, on the other hand, can take effect either when the
|
|
/// dependent type is being added to the route or when it's removed from it,
|
|
/// which is determined by the dependent node's VisitTypePolicy.
|
|
/// In the above example:
|
|
/// - If T3 is required on the same vehicle as T1, A, AR or UV must be on the
|
|
/// same vehicle as a1.
|
|
/// - If T2 is required when adding T1, a2 must be visited *before* a1, and if
|
|
/// r2 is also visited on the route, it must be *after* a1, i.e. T2 must be on
|
|
/// the vehicle when a1 is visited:
|
|
/// ... --> a2 --> ... --> a1 --> ... --> r2 --> ...
|
|
/// - If T3 is required when removing T1, T3 needs to be on the vehicle when
|
|
/// r1 is visited:
|
|
/// ... --> A --> ... --> r1 --> ... OR ... --> r1 --> ... --> UV --> ...
|
|
class TypeRegulationsConstraint : public Constraint {
|
|
public:
|
|
explicit TypeRegulationsConstraint(const RoutingModel& model);
|
|
|
|
void Post() override;
|
|
void InitialPropagate() override;
|
|
|
|
private:
|
|
void PropagateNodeRegulations(int node);
|
|
void CheckRegulationsOnVehicle(int vehicle);
|
|
|
|
const RoutingModel& model_;
|
|
TypeIncompatibilityChecker incompatibility_checker_;
|
|
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_t bound;
|
|
int64_t 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
|
|
/// distance or times.
|
|
///
|
|
/// Quantities at a node are represented by "cumul" variables and the increase
|
|
/// or decrease of quantities between nodes are represented by "transit"
|
|
/// variables. These variables are linked as follows:
|
|
///
|
|
/// if j == next(i),
|
|
/// cumuls(j) = cumuls(i) + transits(i) + slacks(i) +
|
|
/// state_dependent_transits(i)
|
|
///
|
|
/// where slack is a positive slack variable (can represent waiting times for
|
|
/// a time dimension), and state_dependent_transits is a non-purely functional
|
|
/// version of transits_. Favour transits over state_dependent_transits when
|
|
/// possible, because purely functional callbacks allow more optimisations and
|
|
/// make the model faster and easier to solve.
|
|
// TODO(user): Break constraints need to know the service time of nodes
|
|
/// for a given vehicle, it is passed as an external vector, it would be better
|
|
/// to have this information here.
|
|
class RoutingDimension {
|
|
public:
|
|
~RoutingDimension();
|
|
/// Returns the model on which the dimension was created.
|
|
RoutingModel* model() const { return model_; }
|
|
/// Returns the transition value for a given pair of nodes (as var index);
|
|
/// this value is the one taken by the corresponding transit variable when
|
|
/// the 'next' variable for 'from_index' is bound to 'to_index'.
|
|
int64_t GetTransitValue(int64_t from_index, int64_t to_index,
|
|
int64_t vehicle) const;
|
|
/// Same as above but taking a vehicle class of the dimension instead of a
|
|
/// vehicle (the class of a vehicle can be obtained with vehicle_to_class()).
|
|
int64_t GetTransitValueFromClass(int64_t from_index, int64_t to_index,
|
|
int64_t vehicle_class) const {
|
|
return model_->TransitCallback(class_evaluators_[vehicle_class])(from_index,
|
|
to_index);
|
|
}
|
|
/// Get the cumul, transit and slack variables for the given node (given as
|
|
/// int64_t var index).
|
|
IntVar* CumulVar(int64_t index) const { return cumuls_[index]; }
|
|
IntVar* TransitVar(int64_t index) const { return transits_[index]; }
|
|
IntVar* FixedTransitVar(int64_t index) const {
|
|
return fixed_transits_[index];
|
|
}
|
|
IntVar* SlackVar(int64_t index) const { return slacks_[index]; }
|
|
|
|
#if !defined(SWIGPYTHON)
|
|
/// Like CumulVar(), TransitVar(), SlackVar() but return the whole variable
|
|
/// vectors instead (indexed by int64_t var index).
|
|
const std::vector<IntVar*>& cumuls() const { return cumuls_; }
|
|
const std::vector<IntVar*>& fixed_transits() const { return fixed_transits_; }
|
|
const std::vector<IntVar*>& transits() const { return transits_; }
|
|
const std::vector<IntVar*>& slacks() const { return slacks_; }
|
|
#if !defined(SWIGCSHARP) && !defined(SWIGJAVA)
|
|
/// Returns forbidden intervals for each node.
|
|
const std::vector<SortedDisjointIntervalList>& forbidden_intervals() const {
|
|
return forbidden_intervals_;
|
|
}
|
|
/// Returns allowed intervals for a given node in a given interval.
|
|
SortedDisjointIntervalList GetAllowedIntervalsInRange(
|
|
int64_t index, int64_t min_value, int64_t max_value) const;
|
|
/// Returns the smallest value outside the forbidden intervals of node 'index'
|
|
/// that is greater than or equal to a given 'min_value'.
|
|
int64_t GetFirstPossibleGreaterOrEqualValueForNode(int64_t index,
|
|
int64_t min_value) const {
|
|
DCHECK_LT(index, forbidden_intervals_.size());
|
|
const SortedDisjointIntervalList& forbidden_intervals =
|
|
forbidden_intervals_[index];
|
|
const auto first_forbidden_interval_it =
|
|
forbidden_intervals.FirstIntervalGreaterOrEqual(min_value);
|
|
if (first_forbidden_interval_it != forbidden_intervals.end() &&
|
|
min_value >= first_forbidden_interval_it->start) {
|
|
/// min_value is in a forbidden interval.
|
|
return CapAdd(first_forbidden_interval_it->end, 1);
|
|
}
|
|
/// min_value is not forbidden.
|
|
return min_value;
|
|
}
|
|
/// Returns the largest value outside the forbidden intervals of node 'index'
|
|
/// that is less than or equal to a given 'max_value'.
|
|
/// NOTE: If this method is called with a max_value lower than the node's
|
|
/// cumul min, it will return -1.
|
|
int64_t GetLastPossibleLessOrEqualValueForNode(int64_t index,
|
|
int64_t max_value) const {
|
|
DCHECK_LT(index, forbidden_intervals_.size());
|
|
const SortedDisjointIntervalList& forbidden_intervals =
|
|
forbidden_intervals_[index];
|
|
const auto last_forbidden_interval_it =
|
|
forbidden_intervals.LastIntervalLessOrEqual(max_value);
|
|
if (last_forbidden_interval_it != forbidden_intervals.end() &&
|
|
max_value <= last_forbidden_interval_it->end) {
|
|
/// max_value is in a forbidden interval.
|
|
return CapSub(last_forbidden_interval_it->start, 1);
|
|
}
|
|
/// max_value is not forbidden.
|
|
return max_value;
|
|
}
|
|
/// Returns the capacities for all vehicles.
|
|
const std::vector<int64_t>& vehicle_capacities() const {
|
|
return vehicle_capacities_;
|
|
}
|
|
/// Returns the callback evaluating the transit value between two node indices
|
|
/// for a given vehicle.
|
|
const RoutingModel::TransitCallback2& transit_evaluator(int vehicle) const {
|
|
return model_->TransitCallback(
|
|
class_evaluators_[vehicle_to_class_[vehicle]]);
|
|
}
|
|
|
|
/// Returns the callback evaluating the transit value between two node indices
|
|
/// for a given vehicle class.
|
|
const RoutingModel::TransitCallback2& class_transit_evaluator(
|
|
RoutingVehicleClassIndex vehicle_class) const {
|
|
const int vehicle = model_->GetVehicleOfClass(vehicle_class);
|
|
DCHECK_NE(vehicle, -1);
|
|
return transit_evaluator(vehicle);
|
|
}
|
|
|
|
/// Returns the unary callback evaluating the transit value between two node
|
|
/// indices for a given vehicle. If the corresponding callback is not unary,
|
|
/// returns a null callback.
|
|
const RoutingModel::TransitCallback1& GetUnaryTransitEvaluator(
|
|
int vehicle) const {
|
|
return model_->UnaryTransitCallbackOrNull(
|
|
class_evaluators_[vehicle_to_class_[vehicle]]);
|
|
}
|
|
/// Returns true iff the transit evaluator of 'vehicle' is positive for all
|
|
/// arcs.
|
|
bool AreVehicleTransitsPositive(int vehicle) const {
|
|
return model()->is_transit_evaluator_positive_
|
|
[class_evaluators_[vehicle_to_class_[vehicle]]];
|
|
}
|
|
int vehicle_to_class(int vehicle) const { return vehicle_to_class_[vehicle]; }
|
|
#endif /// !defined(SWIGCSHARP) && !defined(SWIGJAVA)
|
|
#endif /// !defined(SWIGPYTHON)
|
|
/// Sets an upper bound on the dimension span on a given vehicle. This is the
|
|
/// preferred way to limit the "length" of the route of a vehicle according to
|
|
/// a dimension.
|
|
void SetSpanUpperBoundForVehicle(int64_t upper_bound, int vehicle);
|
|
/// Sets a cost proportional to the dimension span on a given vehicle,
|
|
/// or on all vehicles at once. "coefficient" must be nonnegative.
|
|
/// This is handy to model costs proportional to idle time when the dimension
|
|
/// represents time.
|
|
/// The cost for a vehicle is
|
|
/// span_cost = coefficient * (dimension end value - dimension start value).
|
|
void SetSpanCostCoefficientForVehicle(int64_t coefficient, int vehicle);
|
|
void SetSpanCostCoefficientForAllVehicles(int64_t coefficient);
|
|
/// Sets a cost proportional to the *global* dimension span, that is the
|
|
/// difference between the largest value of route end cumul variables and
|
|
/// the smallest value of route start cumul variables.
|
|
/// In other words:
|
|
/// global_span_cost =
|
|
/// coefficient * (Max(dimension end value) - Min(dimension start value)).
|
|
void SetGlobalSpanCostCoefficient(int64_t coefficient);
|
|
|
|
#ifndef SWIG
|
|
/// Sets a piecewise linear cost on the cumul variable of a given variable
|
|
/// index. If f is a piecewise linear function, the resulting cost at 'index'
|
|
/// will be f(CumulVar(index)). As of 3/2017, only non-decreasing positive
|
|
/// cost functions are supported.
|
|
void SetCumulVarPiecewiseLinearCost(int64_t index,
|
|
const PiecewiseLinearFunction& cost);
|
|
/// Returns true if a piecewise linear cost has been set for a given variable
|
|
/// index.
|
|
bool HasCumulVarPiecewiseLinearCost(int64_t index) const;
|
|
/// Returns the piecewise linear cost of a cumul variable for a given variable
|
|
/// index. The returned pointer has the same validity as this class.
|
|
const PiecewiseLinearFunction* GetCumulVarPiecewiseLinearCost(
|
|
int64_t index) const;
|
|
#endif
|
|
|
|
/// Sets a soft upper bound to the cumul variable of a given variable index.
|
|
/// If the value of the cumul variable is greater than the bound, a cost
|
|
/// proportional to the difference between this value and the bound is added
|
|
/// to the cost function of the model:
|
|
/// cumulVar <= upper_bound -> cost = 0
|
|
/// cumulVar > upper_bound -> cost = coefficient * (cumulVar - upper_bound)
|
|
/// This is also handy to model tardiness costs when the dimension represents
|
|
/// time.
|
|
void SetCumulVarSoftUpperBound(int64_t index, int64_t upper_bound,
|
|
int64_t coefficient);
|
|
/// Returns true if a soft upper bound has been set for a given variable
|
|
/// index.
|
|
bool HasCumulVarSoftUpperBound(int64_t index) const;
|
|
/// Returns the soft upper bound of a cumul variable for a given variable
|
|
/// index. The "hard" upper bound of the variable is returned if no soft upper
|
|
/// bound has been set.
|
|
int64_t GetCumulVarSoftUpperBound(int64_t index) const;
|
|
/// Returns the cost coefficient of the soft upper bound of a cumul variable
|
|
/// for a given variable index. If no soft upper bound has been set, 0 is
|
|
/// returned.
|
|
int64_t GetCumulVarSoftUpperBoundCoefficient(int64_t index) const;
|
|
|
|
/// Sets a soft lower bound to the cumul variable of a given variable index.
|
|
/// If the value of the cumul variable is less than the bound, a cost
|
|
/// proportional to the difference between this value and the bound is added
|
|
/// to the cost function of the model:
|
|
/// cumulVar > lower_bound -> cost = 0
|
|
/// cumulVar <= lower_bound -> cost = coefficient * (lower_bound -
|
|
/// cumulVar).
|
|
/// This is also handy to model earliness costs when the dimension represents
|
|
/// time.
|
|
void SetCumulVarSoftLowerBound(int64_t index, int64_t lower_bound,
|
|
int64_t coefficient);
|
|
/// Returns true if a soft lower bound has been set for a given variable
|
|
/// index.
|
|
bool HasCumulVarSoftLowerBound(int64_t index) const;
|
|
/// Returns the soft lower bound of a cumul variable for a given variable
|
|
/// index. The "hard" lower bound of the variable is returned if no soft lower
|
|
/// bound has been set.
|
|
int64_t GetCumulVarSoftLowerBound(int64_t index) const;
|
|
/// Returns the cost coefficient of the soft lower bound of a cumul variable
|
|
/// for a given variable index. If no soft lower bound has been set, 0 is
|
|
/// returned.
|
|
int64_t GetCumulVarSoftLowerBoundCoefficient(int64_t 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.
|
|
/// 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_t> 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 most distance.
|
|
/// This supposes that the time until route start and after route end are
|
|
/// infinite breaks.
|
|
void SetBreakDistanceDurationOfVehicle(int64_t distance, int64_t 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) = delays(i, j).
|
|
void SetBreakIntervalsOfVehicle(
|
|
std::vector<IntervalVar*> breaks, int vehicle,
|
|
std::vector<int64_t> node_visit_transits,
|
|
std::function<int64_t(int64_t, int64_t)> delays);
|
|
|
|
/// Returns the break intervals set by SetBreakIntervalsOfVehicle().
|
|
const std::vector<IntervalVar*>& GetBreakIntervalsOfVehicle(
|
|
int vehicle) const;
|
|
/// Returns the pairs (distance, duration) specified by break distance
|
|
/// constraints.
|
|
// clang-format off
|
|
const std::vector<std::pair<int64_t, int64_t> >&
|
|
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_; }
|
|
/// It makes sense to use the function only for self-dependent dimension.
|
|
/// For such dimensions the value of the slack of a node determines the
|
|
/// transition cost of the next transit. Provided that
|
|
/// 1. cumul[node] is fixed,
|
|
/// 2. next[node] and next[next[node]] (if exists) are fixed,
|
|
/// the value of slack[node] for which cumul[next[node]] + transit[next[node]]
|
|
/// is minimized can be found in O(1) using this function.
|
|
int64_t ShortestTransitionSlack(int64_t node) const;
|
|
|
|
/// Returns the name of the dimension.
|
|
const std::string& name() const { return name_; }
|
|
|
|
/// Accessors.
|
|
#ifndef SWIG
|
|
const ReverseArcListGraph<int, int>& GetPathPrecedenceGraph() const {
|
|
return path_precedence_graph_;
|
|
}
|
|
#endif // SWIG
|
|
|
|
/// Limits, in terms of maximum difference between the cumul variables,
|
|
/// between the pickup and delivery alternatives belonging to a single
|
|
/// pickup/delivery pair in the RoutingModel. The indices passed to the
|
|
/// function respectively correspond to the position of the pickup in the
|
|
/// vector of pickup alternatives, and delivery position in the delivery
|
|
/// alternatives for this pickup/delivery pair. These limits should only be
|
|
/// set when each node index appears in at most one pickup/delivery pair, i.e.
|
|
/// each pickup (delivery) index is in a single pickup/delivery pair.first
|
|
/// (pair.second).
|
|
typedef std::function<int64_t(int, int)> PickupToDeliveryLimitFunction;
|
|
|
|
void SetPickupToDeliveryLimitFunctionForPair(
|
|
PickupToDeliveryLimitFunction limit_function, int pair_index);
|
|
|
|
bool HasPickupToDeliveryLimits() const;
|
|
#ifndef SWIG
|
|
int64_t GetPickupToDeliveryLimitForPair(int pair_index, int pickup,
|
|
int delivery) const;
|
|
|
|
struct NodePrecedence {
|
|
int64_t first_node;
|
|
int64_t second_node;
|
|
int64_t offset;
|
|
};
|
|
|
|
void AddNodePrecedence(NodePrecedence precedence) {
|
|
node_precedences_.push_back(precedence);
|
|
}
|
|
const std::vector<NodePrecedence>& GetNodePrecedences() const {
|
|
return node_precedences_;
|
|
}
|
|
#endif // SWIG
|
|
|
|
void AddNodePrecedence(int64_t first_node, int64_t second_node,
|
|
int64_t offset) {
|
|
AddNodePrecedence({first_node, second_node, offset});
|
|
}
|
|
|
|
int64_t GetSpanUpperBoundForVehicle(int vehicle) const {
|
|
return vehicle_span_upper_bounds_[vehicle];
|
|
}
|
|
#ifndef SWIG
|
|
const std::vector<int64_t>& vehicle_span_upper_bounds() const {
|
|
return vehicle_span_upper_bounds_;
|
|
}
|
|
#endif // SWIG
|
|
int64_t GetSpanCostCoefficientForVehicle(int vehicle) const {
|
|
return vehicle_span_cost_coefficients_[vehicle];
|
|
}
|
|
#ifndef SWIG
|
|
int64_t GetSpanCostCoefficientForVehicleClass(
|
|
RoutingVehicleClassIndex vehicle_class) const {
|
|
const int vehicle = model_->GetVehicleOfClass(vehicle_class);
|
|
DCHECK_NE(vehicle, -1);
|
|
return GetSpanCostCoefficientForVehicle(vehicle);
|
|
}
|
|
#endif // SWIG
|
|
#ifndef SWIG
|
|
const std::vector<int64_t>& vehicle_span_cost_coefficients() const {
|
|
return vehicle_span_cost_coefficients_;
|
|
}
|
|
#endif // SWIG
|
|
int64_t global_span_cost_coefficient() const {
|
|
return global_span_cost_coefficient_;
|
|
}
|
|
|
|
int64_t GetGlobalOptimizerOffset() const {
|
|
DCHECK_GE(global_optimizer_offset_, 0);
|
|
return global_optimizer_offset_;
|
|
}
|
|
int64_t GetLocalOptimizerOffsetForVehicle(int vehicle) const {
|
|
if (vehicle >= local_optimizer_offset_for_vehicle_.size()) {
|
|
return 0;
|
|
}
|
|
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_ = std::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);
|
|
}
|
|
/// If the span of vehicle on this dimension is larger than bound,
|
|
/// the cost will be increased by cost * (span - bound)^2.
|
|
void SetQuadraticCostSoftSpanUpperBoundForVehicle(
|
|
SimpleBoundCosts::BoundCost bound_cost, int vehicle) {
|
|
if (!HasQuadraticCostSoftSpanUpperBounds()) {
|
|
vehicle_quadratic_cost_soft_span_upper_bound_ =
|
|
std::make_unique<SimpleBoundCosts>(
|
|
model_->vehicles(), SimpleBoundCosts::BoundCost{kint64max, 0});
|
|
}
|
|
vehicle_quadratic_cost_soft_span_upper_bound_->bound_cost(vehicle) =
|
|
bound_cost;
|
|
}
|
|
bool HasQuadraticCostSoftSpanUpperBounds() const {
|
|
return vehicle_quadratic_cost_soft_span_upper_bound_ != nullptr;
|
|
}
|
|
SimpleBoundCosts::BoundCost GetQuadraticCostSoftSpanUpperBoundForVehicle(
|
|
int vehicle) const {
|
|
DCHECK(HasQuadraticCostSoftSpanUpperBounds());
|
|
return vehicle_quadratic_cost_soft_span_upper_bound_->bound_cost(vehicle);
|
|
}
|
|
#endif /// !defined SWIG
|
|
|
|
private:
|
|
struct SoftBound {
|
|
IntVar* var;
|
|
int64_t bound;
|
|
int64_t coefficient;
|
|
};
|
|
|
|
struct PiecewiseLinearCost {
|
|
PiecewiseLinearCost() : var(nullptr), cost(nullptr) {}
|
|
IntVar* var;
|
|
std::unique_ptr<PiecewiseLinearFunction> cost;
|
|
};
|
|
|
|
class SelfBased {};
|
|
RoutingDimension(RoutingModel* model, std::vector<int64_t> vehicle_capacities,
|
|
const std::string& name,
|
|
const RoutingDimension* base_dimension);
|
|
RoutingDimension(RoutingModel* model, std::vector<int64_t> vehicle_capacities,
|
|
const std::string& name, SelfBased);
|
|
void Initialize(const std::vector<int>& transit_evaluators,
|
|
const std::vector<int>& state_dependent_transit_evaluators,
|
|
int64_t slack_max);
|
|
void InitializeCumuls();
|
|
void InitializeTransits(
|
|
const std::vector<int>& transit_evaluators,
|
|
const std::vector<int>& state_dependent_transit_evaluators,
|
|
int64_t slack_max);
|
|
void InitializeTransitVariables(int64_t slack_max);
|
|
/// Sets up the cost variables related to cumul soft upper bounds.
|
|
void SetupCumulVarSoftUpperBoundCosts(
|
|
std::vector<IntVar*>* cost_elements) const;
|
|
/// Sets up the cost variables related to cumul soft lower bounds.
|
|
void SetupCumulVarSoftLowerBoundCosts(
|
|
std::vector<IntVar*>* cost_elements) const;
|
|
void SetupCumulVarPiecewiseLinearCosts(
|
|
std::vector<IntVar*>* cost_elements) const;
|
|
/// Sets up the cost variables related to the global span and per-vehicle span
|
|
/// costs (only for the "slack" part of the latter).
|
|
void SetupGlobalSpanCost(std::vector<IntVar*>* cost_elements) const;
|
|
void SetupSlackAndDependentTransitCosts() const;
|
|
/// Finalize the model of the dimension.
|
|
void CloseModel(bool use_light_propagation);
|
|
|
|
void SetOffsetForGlobalOptimizer(int64_t offset) {
|
|
global_optimizer_offset_ = std::max(Zero(), offset);
|
|
}
|
|
/// Moves elements of "offsets" into vehicle_offsets_for_local_optimizer_.
|
|
void SetVehicleOffsetsForLocalOptimizer(std::vector<int64_t> offsets) {
|
|
/// Make sure all offsets are positive.
|
|
std::transform(offsets.begin(), offsets.end(), offsets.begin(),
|
|
[](int64_t offset) { return std::max(Zero(), offset); });
|
|
local_optimizer_offset_for_vehicle_ = std::move(offsets);
|
|
}
|
|
|
|
std::vector<IntVar*> cumuls_;
|
|
std::vector<SortedDisjointIntervalList> forbidden_intervals_;
|
|
std::vector<IntVar*> capacity_vars_;
|
|
const std::vector<int64_t> vehicle_capacities_;
|
|
std::vector<IntVar*> transits_;
|
|
std::vector<IntVar*> fixed_transits_;
|
|
/// Values in class_evaluators_ correspond to the evaluators in
|
|
/// RoutingModel::transit_evaluators_ for each vehicle class.
|
|
std::vector<int> class_evaluators_;
|
|
std::vector<int64_t> vehicle_to_class_;
|
|
#ifndef SWIG
|
|
ReverseArcListGraph<int, int> path_precedence_graph_;
|
|
#endif
|
|
// For every {first_node, second_node, offset} element in node_precedences_,
|
|
// if both first_node and second_node are performed, then
|
|
// cumuls_[second_node] must be greater than (or equal to)
|
|
// cumuls_[first_node] + offset.
|
|
std::vector<NodePrecedence> node_precedences_;
|
|
|
|
// 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 example for this is a time dimension.
|
|
const RoutingDimension* const base_dimension_;
|
|
|
|
// Values in state_dependent_class_evaluators_ correspond to the evaluators
|
|
// in RoutingModel::state_dependent_transit_evaluators_ for each vehicle
|
|
// class.
|
|
std::vector<int> state_dependent_class_evaluators_;
|
|
std::vector<int64_t> state_dependent_vehicle_to_class_;
|
|
|
|
// For each pickup/delivery pair_index for which limits have been set,
|
|
// pickup_to_delivery_limits_per_pair_index_[pair_index] contains the
|
|
// PickupToDeliveryLimitFunction for the pickup and deliveries in this pair.
|
|
std::vector<PickupToDeliveryLimitFunction>
|
|
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<std::pair<int64_t, int64_t> > >
|
|
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_;
|
|
std::vector<int64_t> vehicle_span_upper_bounds_;
|
|
int64_t global_span_cost_coefficient_;
|
|
std::vector<int64_t> vehicle_span_cost_coefficients_;
|
|
std::vector<SoftBound> cumul_var_soft_upper_bound_;
|
|
std::vector<SoftBound> cumul_var_soft_lower_bound_;
|
|
std::vector<PiecewiseLinearCost> cumul_var_piecewise_linear_cost_;
|
|
RoutingModel* const model_;
|
|
const std::string name_;
|
|
int64_t global_optimizer_offset_;
|
|
std::vector<int64_t> local_optimizer_offset_for_vehicle_;
|
|
/// nullptr if not defined.
|
|
std::unique_ptr<SimpleBoundCosts> vehicle_soft_span_upper_bound_;
|
|
std::unique_ptr<SimpleBoundCosts>
|
|
vehicle_quadratic_cost_soft_span_upper_bound_;
|
|
friend class RoutingModel;
|
|
friend class RoutingModelInspector;
|
|
friend void AppendDimensionCumulFilters(
|
|
const std::vector<RoutingDimension*>& dimensions,
|
|
const RoutingSearchParameters& parameters, bool filter_objective_cost,
|
|
std::vector<LocalSearchFilterManager::FilterEvent>* filters);
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(RoutingDimension);
|
|
};
|
|
|
|
/// A decision builder which tries to assign values to variables as close as
|
|
/// possible to target values first.
|
|
DecisionBuilder* MakeSetValuesFromTargets(Solver* solver,
|
|
std::vector<IntVar*> variables,
|
|
std::vector<int64_t> targets);
|
|
|
|
/// 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(const RoutingModel& model,
|
|
const RoutingSearchParameters& search_parameters,
|
|
const Assignment* initial_solution,
|
|
Assignment* solution);
|
|
|
|
#if !defined(SWIG)
|
|
IntVarLocalSearchFilter* MakeVehicleBreaksFilter(
|
|
const RoutingModel& routing_model, const RoutingDimension& dimension);
|
|
|
|
// A decision builder that monitors solutions, and tries to fix dimension
|
|
// variables whose route did not change in the candidate solution.
|
|
// Dimension variables are Cumul, Slack and break variables of all dimensions.
|
|
// The user must make sure that those variables will be always be fixed at
|
|
// solution, typically by composing another DecisionBuilder after this one.
|
|
// If this DecisionBuilder returns a non-nullptr value at some node of the
|
|
// search tree, it will always return nullptr in the subtree of that node.
|
|
// Moreover, the decision will be a simultaneous assignment of the dimension
|
|
// variables of unchanged routes on the left branch, and an empty decision on
|
|
// the right branch.
|
|
DecisionBuilder* MakeRestoreDimensionValuesForUnchangedRoutes(
|
|
RoutingModel* model);
|
|
#endif
|
|
|
|
} // namespace operations_research
|
|
#endif // OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_H_
|