3688 lines
172 KiB
C++
3688 lines
172 KiB
C++
// Copyright 2010-2025 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.
|
|
|
|
/** @file routing.h
|
|
@brief 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.
|
|
@details 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.\n
|
|
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.\n
|
|
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://en.wikipedia.org/wiki/Vehicle_routing_problem
|
|
|
|
The vehicle routing library is a vertical layer above the constraint
|
|
programming library (ortools/constraint_programming:cp).\n
|
|
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.\n
|
|
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:\n
|
|
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:
|
|
@code{.cpp}
|
|
int64_t MyDistance(int64_t from, int64_t to) {
|
|
return from + to;
|
|
}
|
|
@endcode
|
|
- Create a routing model for a given problem size (int number of nodes) and
|
|
number of routes (here, 1):
|
|
@code{.cpp}
|
|
RoutingIndexManager manager(...number of nodes..., 1);
|
|
RoutingModel routing(manager);
|
|
@endcode
|
|
- 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.
|
|
@code{.cpp}
|
|
const int cost = routing.RegisterTransitCallback(MyDistance);
|
|
routing.SetArcCostEvaluatorOfAllVehicles(cost);
|
|
@endcode
|
|
- Find a solution using Solve(), returns a solution if any (owned by
|
|
routing):
|
|
@code{.cpp}
|
|
const Assignment* solution = routing.Solve();
|
|
CHECK(solution != nullptr);
|
|
@endcode
|
|
- Inspect the solution cost and route (only one route here):
|
|
@code{.cpp}
|
|
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);
|
|
}
|
|
@endcode
|
|
Keywords: Vehicle Routing, Traveling Salesman Problem, TSP, VRP, CVRPTW, PDP.
|
|
*/
|
|
|
|
#ifndef ORTOOLS_CONSTRAINT_SOLVER_ROUTING_H_
|
|
#define ORTOOLS_CONSTRAINT_SOLVER_ROUTING_H_
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <optional>
|
|
#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/functional/any_invocable.h"
|
|
#include "absl/hash/hash.h"
|
|
#include "absl/log/check.h"
|
|
#include "absl/strings/string_view.h"
|
|
#include "absl/time/time.h"
|
|
#include "absl/types/span.h"
|
|
#include "ortools/base/logging.h"
|
|
#include "ortools/base/strong_vector.h"
|
|
#include "ortools/base/types.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_heuristic_parameters.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/constraint_solver/routing_utils.h"
|
|
#include "ortools/graph/graph.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 FinalizerVariables;
|
|
class GlobalDimensionCumulOptimizer;
|
|
class LocalDimensionCumulOptimizer;
|
|
#ifndef SWIG
|
|
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];
|
|
}
|
|
int NumPaths() const { return start_of_path_.size(); }
|
|
const std::vector<int64_t>& Paths() const { return path_of_node_; }
|
|
const std::vector<int64_t>& Starts() const { return start_of_path_; }
|
|
int64_t Start(int path) const { return start_of_path_[path]; }
|
|
int64_t End(int path) const { return end_of_path_[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_;
|
|
};
|
|
|
|
struct RoutingSearchStats {
|
|
int64_t num_cp_sat_calls_in_lp_scheduling = 0;
|
|
int64_t num_glop_calls_in_lp_scheduling = 0;
|
|
int64_t num_min_cost_flow_calls = 0;
|
|
int64_t num_cp_sat_calls_in_routing = 0;
|
|
int64_t num_generalized_cp_sat_calls_in_routing = 0;
|
|
};
|
|
|
|
class OR_DLL RoutingModel {
|
|
public:
|
|
/// 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 RoutingResourceClassIndex ResourceClassIndex;
|
|
typedef RoutingTransitCallback1 TransitCallback1;
|
|
typedef RoutingTransitCallback2 TransitCallback2;
|
|
typedef RoutingCumulDependentTransitCallback2 CumulDependentTransitCallback2;
|
|
|
|
#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;
|
|
// TODO(user): replace span_cost_coefficient by
|
|
// transit_cost_coefficient and add the span coefficient to the slack one.
|
|
int64_t span_cost_coefficient;
|
|
int64_t slack_cost_coefficient;
|
|
const RoutingDimension* dimension;
|
|
bool operator<(const DimensionCost& cost) const {
|
|
return std::tie(transit_evaluator_class, span_cost_coefficient,
|
|
slack_cost_coefficient) <
|
|
std::tie(cost.transit_evaluator_class,
|
|
cost.span_cost_coefficient,
|
|
cost.slack_cost_coefficient);
|
|
}
|
|
|
|
friend bool operator==(const DimensionCost& c1, const DimensionCost& c2) {
|
|
return c1.transit_evaluator_class == c2.transit_evaluator_class &&
|
|
c1.span_cost_coefficient == c2.span_cost_coefficient &&
|
|
c1.slack_cost_coefficient == c2.slack_cost_coefficient;
|
|
}
|
|
template <typename H>
|
|
friend H AbslHashValue(H h, const DimensionCost& cost) {
|
|
return H::combine(std::move(h), cost.transit_evaluator_class,
|
|
cost.span_cost_coefficient,
|
|
cost.slack_cost_coefficient);
|
|
}
|
|
};
|
|
std::vector<DimensionCost>
|
|
dimension_transit_evaluator_class_and_cost_coefficient;
|
|
|
|
explicit CostClass(int evaluator_index)
|
|
: evaluator_index(evaluator_index) {}
|
|
|
|
friend bool operator==(const CostClass& c1, const CostClass& c2) {
|
|
return c1.evaluator_index == c2.evaluator_index &&
|
|
c1.dimension_transit_evaluator_class_and_cost_coefficient ==
|
|
c2.dimension_transit_evaluator_class_and_cost_coefficient;
|
|
}
|
|
template <typename H>
|
|
friend H AbslHashValue(H h, const CostClass& c) {
|
|
return H::combine(
|
|
std::move(h), c.evaluator_index,
|
|
c.dimension_transit_evaluator_class_and_cost_coefficient);
|
|
}
|
|
};
|
|
#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_; }
|
|
|
|
friend bool operator==(const Attributes& a, const Attributes& b) {
|
|
return a.start_domain_ == b.start_domain_ &&
|
|
a.end_domain_ == b.end_domain_;
|
|
}
|
|
template <typename H>
|
|
friend H AbslHashValue(H h, const Attributes& attributes) {
|
|
return H::combine(std::move(h), attributes.start_domain_,
|
|
attributes.end_domain_);
|
|
}
|
|
|
|
private:
|
|
/// The following domains constrain the dimension start/end cumul of
|
|
/// 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;
|
|
};
|
|
|
|
/// 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];
|
|
}
|
|
|
|
void SetAllowedResourcesForVehicle(
|
|
int vehicle, const std::vector<int>& allowed_resource_indices) {
|
|
DCHECK(!model_->closed_);
|
|
// NOTE: As of 12/2023, an empty set of allowed resources means "all
|
|
// resources allowed", so we make sure the empty set isn't used here
|
|
// explicitly by the user to mark a vehicle as "unusable".
|
|
DCHECK(!allowed_resource_indices.empty());
|
|
DCHECK(vehicle_requires_resource_[vehicle]);
|
|
DCHECK_LT(vehicle, vehicle_allowed_resources_.size());
|
|
vehicle_allowed_resources_[vehicle].clear();
|
|
vehicle_allowed_resources_[vehicle].insert(
|
|
allowed_resource_indices.begin(), allowed_resource_indices.end());
|
|
}
|
|
void ClearAllowedResourcesForVehicle(int vehicle) {
|
|
DCHECK_LT(vehicle, vehicle_allowed_resources_.size());
|
|
vehicle_allowed_resources_[vehicle].clear();
|
|
}
|
|
const absl::flat_hash_set<int>& GetResourcesMarkedAllowedForVehicle(
|
|
int vehicle) const {
|
|
DCHECK_LT(vehicle, vehicle_allowed_resources_.size());
|
|
return vehicle_allowed_resources_[vehicle];
|
|
}
|
|
bool IsResourceAllowedForVehicle(int resource, int vehicle) const {
|
|
DCHECK_LT(vehicle, vehicle_allowed_resources_.size());
|
|
return vehicle_allowed_resources_[vehicle].empty() ||
|
|
vehicle_allowed_resources_[vehicle].contains(resource);
|
|
}
|
|
|
|
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 GetResourceClassesCount() const {
|
|
return resource_indices_per_class_.size();
|
|
}
|
|
const std::vector<int>& GetResourceIndicesInClass(
|
|
ResourceClassIndex resource_class) const {
|
|
DCHECK_LT(resource_class, resource_indices_per_class_.size());
|
|
return resource_indices_per_class_[resource_class];
|
|
}
|
|
// clang-format off
|
|
const util_intops::StrongVector<ResourceClassIndex, std::vector<int> >&
|
|
GetResourceIndicesPerClass() const {
|
|
return resource_indices_per_class_;
|
|
}
|
|
// clang-format on
|
|
ResourceClassIndex GetResourceClassIndex(int resource_index) const {
|
|
DCHECK_LT(resource_index, resource_class_indices_.size());
|
|
return resource_class_indices_[resource_index];
|
|
}
|
|
const Attributes& GetDimensionAttributesForClass(
|
|
const RoutingDimension* dimension, ResourceClassIndex rc_index) const {
|
|
DCHECK_LT(rc_index, resource_indices_per_class_.size());
|
|
const std::vector<int>& resource_indices =
|
|
resource_indices_per_class_[rc_index];
|
|
DCHECK(!resource_indices.empty());
|
|
return resources_[resource_indices[0]].GetDimensionAttributes(dimension);
|
|
}
|
|
|
|
int Size() const { return resources_.size(); }
|
|
int Index() const { return index_; }
|
|
|
|
private:
|
|
explicit ResourceGroup(const RoutingModel* model)
|
|
: index_(model->GetResourceGroups().size()),
|
|
model_(model),
|
|
vehicle_requires_resource_(model->vehicles(), false),
|
|
vehicle_allowed_resources_(model->vehicles()) {}
|
|
|
|
void ComputeResourceClasses();
|
|
|
|
const int index_;
|
|
const RoutingModel* const model_;
|
|
std::vector<Resource> resources_;
|
|
// Stores the ResourceClassIndex of each resource (See implementation of
|
|
// ComputeResourceClasses()).
|
|
std::vector<ResourceClassIndex> resource_class_indices_;
|
|
// clang-format off
|
|
util_intops::StrongVector<ResourceClassIndex, std::vector<int> >
|
|
resource_indices_per_class_;
|
|
// clang-format on
|
|
|
|
std::vector<bool> vehicle_requires_resource_;
|
|
std::vector<int> vehicles_requiring_resource_;
|
|
// For each vehicle, stores the set of allowed resource indices if it's
|
|
// restricted for that vehicle. If the set is empty for a vehicle, then any
|
|
// resource from this group can be assigned to it.
|
|
// clang-format off
|
|
std::vector<absl::flat_hash_set<int> > vehicle_allowed_resources_;
|
|
// clang-format on
|
|
/// All indices of dimensions affected by this resource group.
|
|
absl::flat_hash_set<DimensionIndex> affected_dimension_indices_;
|
|
|
|
friend class RoutingModel;
|
|
};
|
|
|
|
/// Struct used to store a variable value.
|
|
struct VariableValuePair {
|
|
int var_index;
|
|
int64_t value;
|
|
};
|
|
|
|
/// Class used to solve a secondary model within a first solution strategy.
|
|
class SecondaryOptimizer {
|
|
public:
|
|
SecondaryOptimizer(RoutingModel* model,
|
|
RoutingSearchParameters* search_parameters,
|
|
int64_t solve_period);
|
|
bool Solve(const std::vector<RoutingModel::VariableValuePair>& in_state,
|
|
std::vector<RoutingModel::VariableValuePair>* out_state);
|
|
|
|
private:
|
|
RoutingModel* const model_;
|
|
const RoutingSearchParameters* const search_parameters_;
|
|
const int64_t solve_period_ = 0;
|
|
int64_t call_count_ = 0;
|
|
Assignment* state_ = nullptr;
|
|
absl::flat_hash_map<IntVar*, int> var_to_index_;
|
|
};
|
|
|
|
/// 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);
|
|
|
|
// This type is neither copyable nor movable.
|
|
RoutingModel(const RoutingModel&) = delete;
|
|
RoutingModel& operator=(const RoutingModel&) = delete;
|
|
|
|
~RoutingModel();
|
|
|
|
/// Represents the sign of values returned by a transit evaluator.
|
|
enum TransitEvaluatorSign {
|
|
kTransitEvaluatorSignUnknown = 0,
|
|
kTransitEvaluatorSignPositiveOrZero = 1,
|
|
kTransitEvaluatorSignNegativeOrZero = 2,
|
|
};
|
|
/// Registers 'callback' and returns its index.
|
|
/// The sign parameter allows to notify the solver that the callback only
|
|
/// return values of the given sign. This can help the solver, but passing
|
|
/// an incorrect sign may crash in non-opt compilation mode, and yield
|
|
/// incorrect results in opt.
|
|
int RegisterUnaryTransitVector(std::vector<int64_t> values);
|
|
int RegisterUnaryTransitCallback(
|
|
TransitCallback1 callback,
|
|
TransitEvaluatorSign sign = kTransitEvaluatorSignUnknown);
|
|
|
|
int RegisterTransitMatrix(
|
|
std::vector<std::vector<int64_t> /*needed_for_swig*/> values);
|
|
int RegisterTransitCallback(
|
|
TransitCallback2 callback,
|
|
TransitEvaluatorSign sign = kTransitEvaluatorSignUnknown);
|
|
int RegisterCumulDependentTransitCallback(
|
|
CumulDependentTransitCallback2 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 CumulDependentTransitCallback2& CumulDependentTransitCallback(
|
|
int callback_index) const {
|
|
CHECK_LT(callback_index, cumul_dependent_transit_evaluators_.size());
|
|
return cumul_dependent_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, j) + 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 on arc i->j is the sum of:
|
|
/// - A "fixed" transit value, obtained from the fixed_evaluator_index for
|
|
/// this vehicle, referencing evaluators in transit_evaluators_, and
|
|
/// - A FloatSlopePiecewiseLinearFunction of the cumul of node i, obtained
|
|
/// from the cumul_dependent_evaluator_index of this vehicle, pointing to
|
|
/// an evaluator in cumul_dependent_transit_evaluators_.
|
|
bool AddDimensionWithCumulDependentVehicleTransitAndCapacity(
|
|
const std::vector<int>& fixed_evaluator_indices,
|
|
const std::vector<int>& cumul_dependent_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);
|
|
|
|
/// 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 dimensions for which all transit evaluators are unary.
|
|
std::vector<RoutingDimension*> GetUnaryDimensions() 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(absl::string_view 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(absl::string_view 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 and returns a pointer to it.
|
|
ResourceGroup* 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];
|
|
}
|
|
|
|
/// The following enum is used to describe how the penalty cost is computed
|
|
/// when using @ref AddDisjunction.
|
|
enum PenaltyCostBehavior { PENALIZE_ONCE, PENALIZE_PER_INACTIVE };
|
|
|
|
/// @brief Adds a disjunction constraint on the indices.
|
|
/// @details Exactly 'max_cardinality' of the indices are active.
|
|
///
|
|
/// 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 if
|
|
/// the penalty cost is set to `PENALIZE_PER_INACTIVE`.
|
|
/// This is equivalent to adding the constraint:
|
|
/// p + Sum(i)active[i] == max_cardinality
|
|
/// where p is an integer variable.
|
|
/// If the penalty cost is set to `PENALIZE_ONCE`, then 'penalty' is payed
|
|
/// once if there are less than `max_cardinality` of the indices active.
|
|
/// This is equivalent to adding the constraint:
|
|
/// p == (Sum(i)active[i] != max_cardinality)
|
|
/// where p is a boolean variable.
|
|
/// The following cost is added to the cost function: p * penalty.
|
|
/// @param[in] 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.
|
|
/// @warning Start and end indices of any vehicle cannot be part of a
|
|
/// disjunction.
|
|
DisjunctionIndex AddDisjunction(const std::vector<int64_t>& indices,
|
|
int64_t penalty = kNoPenalty,
|
|
int64_t max_cardinality = 1,
|
|
PenaltyCostBehavior penalty_cost_behavior =
|
|
PenaltyCostBehavior::PENALIZE_ONCE);
|
|
/// 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 @ref PenaltyCostBehavior used by the disjunction of index
|
|
/// 'index'.
|
|
PenaltyCostBehavior GetDisjunctionPenaltyCostBehavior(
|
|
DisjunctionIndex index) const {
|
|
return disjunctions_[index].value.penalty_cost_behavior;
|
|
}
|
|
/// 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.
|
|
/// TODO(user): Extend this to allow nodes/indices to be on the same given
|
|
/// set of vehicle.
|
|
void AddSoftSameVehicleConstraint(std::vector<int64_t> indices, int64_t cost);
|
|
/// Returns the number of soft same vehicle constraints in the model.
|
|
int GetNumberOfSoftSameVehicleConstraints() const {
|
|
return same_vehicle_costs_.size();
|
|
}
|
|
/// Returns the indices of the nodes in the soft same vehicle constraint of
|
|
/// index 'index'.
|
|
const std::vector<int64_t>& GetSoftSameVehicleIndices(int index) const {
|
|
return same_vehicle_costs_[index].indices;
|
|
}
|
|
/// Returns the cost of the soft same vehicle constraint of index 'index'.
|
|
int64_t GetSoftSameVehicleCost(int index) const {
|
|
return same_vehicle_costs_[index].value;
|
|
}
|
|
|
|
/// 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(absl::Span<const int> vehicles,
|
|
int64_t index);
|
|
|
|
/// Returns true if a vehicle is allowed to visit a given node.
|
|
bool IsVehicleAllowedForIndex(int vehicle, int64_t index) const {
|
|
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);
|
|
|
|
/// The position of a node in the set of pickup and delivery pairs.
|
|
struct PickupDeliveryPosition {
|
|
/// The index of the pickup and delivery pair within which the node appears.
|
|
int pd_pair_index = -1;
|
|
/// The index of the node in the vector of pickup (resp. delivery)
|
|
/// alternatives of the pair.
|
|
int alternative_index = -1;
|
|
};
|
|
/// Returns the pickup and delivery positions where the node is a pickup.
|
|
std::optional<PickupDeliveryPosition> GetPickupPosition(
|
|
int64_t node_index) const;
|
|
/// Returns the pickup and delivery positions where the node is a delivery.
|
|
std::optional<PickupDeliveryPosition> GetDeliveryPosition(
|
|
int64_t node_index) const;
|
|
/// Returns whether the node is a pickup (resp. delivery).
|
|
bool IsPickup(int64_t node_index) const {
|
|
return index_to_pickup_position_[node_index].pd_pair_index != -1;
|
|
}
|
|
bool IsDelivery(int64_t node_index) const {
|
|
return index_to_delivery_position_[node_index].pd_pair_index != -1;
|
|
}
|
|
|
|
/// 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 std::vector<PickupDeliveryPair>& 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 std::vector<PickupDeliveryPair>&
|
|
GetImplicitUniquePickupAndDeliveryPairs() const {
|
|
DCHECK(closed_);
|
|
return implicit_pickup_delivery_pairs_without_alternatives_;
|
|
}
|
|
#endif // SWIG
|
|
|
|
// Returns the first pickup or delivery sibling of the given node matching
|
|
// the given predicate.
|
|
std::optional<int64_t> GetFirstMatchingPickupDeliverySibling(
|
|
int64_t node, const std::function<bool(int64_t)>& is_match) const;
|
|
|
|
/// Set the node visit types and incompatibilities/requirements between the
|
|
/// types (see below).
|
|
///
|
|
/// NOTE: Before adding any incompatibilities and/or requirements on types,
|
|
/// all corresponding node types must have been set.
|
|
///
|
|
/// 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;
|
|
#ifndef SWIG
|
|
const std::vector<std::vector<int>>& GetVisitTypeComponents() const {
|
|
return visit_type_components_;
|
|
}
|
|
#endif // SWIG
|
|
int GetNumberOfVisitTypes() const { return num_visit_types_; }
|
|
#ifndef SWIG
|
|
const std::vector<std::vector<int>>& GetTopologicallySortedVisitTypes()
|
|
const {
|
|
DCHECK(closed_);
|
|
return topologically_sorted_visit_types_;
|
|
}
|
|
const std::vector<std::vector<std::vector<int>>>&
|
|
GetTopologicallySortedNodePrecedences() const {
|
|
DCHECK(closed_);
|
|
return topologically_sorted_node_precedences_;
|
|
}
|
|
#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.
|
|
/// NOTE: To avoid unnecessary memory reallocations, it is recommended to only
|
|
/// add incompatibilities once all the existing types have been set with
|
|
/// SetVisitType().
|
|
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 !hard_incompatible_types_per_type_index_.empty();
|
|
}
|
|
bool HasTemporalTypeIncompatibilities() const {
|
|
return !temporal_incompatible_types_per_type_index_.empty();
|
|
}
|
|
/// 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.
|
|
/// NOTE: To avoid unnecessary memory reallocations, it is recommended to only
|
|
/// add requirements once all the existing types have been set with
|
|
/// SetVisitType().
|
|
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 !same_vehicle_required_type_alternatives_per_type_index_.empty();
|
|
}
|
|
bool HasTemporalTypeRequirements() const {
|
|
return !required_type_alternatives_when_adding_type_index_.empty() ||
|
|
!required_type_alternatives_when_removing_type_index_.empty();
|
|
}
|
|
|
|
/// 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;
|
|
// Sets the energy cost of a vehicle.
|
|
// The energy used by a vehicle is defined as the integral of the
|
|
// force dimension over the distance dimension:
|
|
// it is the sum over nodes visited by the vehicle of
|
|
// force.CumulVar(Next(node)) * distance.TransitVar(node).
|
|
// The energy cost of a vehicle is linear in the energy used by the vehicle,
|
|
// this call sets the coefficient to cost_per_unit. The cost is zero if unset.
|
|
void SetPathEnergyCostOfVehicle(const std::string& force,
|
|
const std::string& distance,
|
|
int64_t cost_per_unit, int vehicle);
|
|
// Sets the energy cost of a vehicle, relative to a threshold.
|
|
// The cost per unit of energy is cost_per_unit_below_threshold until the
|
|
// force reaches the threshold, then it is cost_per_unit_above_threshold:
|
|
// min(threshold, force.CumulVar(Next(node))) * distance.TransitVar(node) *
|
|
// cost_per_unit_below_threshold + max(0, force.CumulVar(Next(node)) -
|
|
// threshold) * distance.TransitVar(node) * cost_per_unit_above_threshold.
|
|
void SetPathEnergyCostsOfVehicle(const std::string& force,
|
|
const std::string& distance,
|
|
int64_t threshold,
|
|
int64_t cost_per_unit_below_threshold,
|
|
int64_t cost_per_unit_above_threshold,
|
|
int vehicle);
|
|
|
|
/// 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_;
|
|
}
|
|
|
|
// Adds a custom route constraint based on a route evaluation callback. The
|
|
// callback must not return a value if the route vector is invalid, and
|
|
// returns the value of the route otherwise.
|
|
// The callback must always return the same value for a given route.
|
|
#ifndef SWIG
|
|
void AddRouteConstraint(
|
|
absl::AnyInvocable<std::optional<int64_t>(const std::vector<int64_t>&)>
|
|
route_evaluator,
|
|
bool costs_are_homogeneous_across_vehicles = false);
|
|
#endif
|
|
std::optional<int64_t> GetRouteCost(const std::vector<int64_t>& route) const {
|
|
int64_t route_cost = 0;
|
|
for (auto& evaluator : route_evaluators_) {
|
|
std::optional<int64_t> cost = evaluator(route);
|
|
if (!cost.has_value()) return std::nullopt;
|
|
CapAddTo(cost.value(), &route_cost);
|
|
}
|
|
return route_cost;
|
|
}
|
|
|
|
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 hint to be used by first solution strategies. The hint assignment
|
|
/// must outlive the search.
|
|
/// As of 2024-12, only used by LOCAL_CHEAPEST_INSERTION and
|
|
/// LOCAL_CHEAPEST_COST_INSERTION.
|
|
void SetFirstSolutionHint(const Assignment* hint) { hint_ = hint; }
|
|
/// Returns the current hint assignment.
|
|
const Assignment* GetFirstSolutionHint() const { return hint_; }
|
|
/// 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* monitor);
|
|
// Adds a callback called at the beginning of the search.
|
|
void AddEnterSearchCallback(std::function<void()> callback);
|
|
/// 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.
|
|
/// If track_unchecked_neighbors is true, the callback will also be called on
|
|
/// AcceptUncheckedNeighbor() events, which is useful to grab solutions
|
|
/// obtained when solver_parameters.check_solution_period > 1 (aka fastLS).
|
|
void AddAtSolutionCallback(std::function<void()> callback,
|
|
bool track_unchecked_neighbors = false);
|
|
// Internal-only: Adds a callback to reset
|
|
// RestoreDimensionValuesForUnchangedRoutes at the beginning of the search.
|
|
void AddRestoreDimensionValuesResetCallback(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);
|
|
/// Improves a given assignment using unchecked local search.
|
|
/// If check_solution_in_cp is true the final solution will be checked with
|
|
/// the CP solver.
|
|
/// As of 11/2023, only works with greedy descent.
|
|
const Assignment* FastSolveFromAssignmentWithParameters(
|
|
const Assignment* assignment,
|
|
const RoutingSearchParameters& search_parameters,
|
|
bool check_solution_in_cp,
|
|
absl::flat_hash_set<IntVar*>* touched = 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);
|
|
/// Solves the current routing model by using an Iterated Local Search
|
|
/// approach.
|
|
const Assignment* SolveWithIteratedLocalSearch(
|
|
const RoutingSearchParameters& search_parameters);
|
|
/// 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);
|
|
/// Returns detailed search statistics.
|
|
operations_research::SubSolverStatistics GetSubSolverStatistics() const;
|
|
/// 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 lower bound found by internal solvers during the
|
|
/// search.
|
|
int64_t objective_lower_bound() const { return objective_lower_bound_; }
|
|
/// Returns the current status of the routing model.
|
|
RoutingSearchStatus::Value status() const { return status_; }
|
|
/// Returns search statistics.
|
|
const RoutingSearchStats& search_stats() const { return search_stats_; }
|
|
/// 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* 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* 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>>* 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* var);
|
|
void AddIntervalToAssignment(IntervalVar* 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);
|
|
|
|
#ifndef SWIG
|
|
/// 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 {
|
|
/// Models the (real) travel value Tᵣ, for this transition based on the
|
|
/// departure value of the travel.
|
|
FloatSlopePiecewiseLinearFunction 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.
|
|
FloatSlopePiecewiseLinearFunction 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;
|
|
};
|
|
|
|
#endif // SWIG
|
|
|
|
#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
|
|
struct NodeNeighborsParameters {
|
|
int num_neighbors;
|
|
bool add_vehicle_starts_to_neighbors = true;
|
|
bool add_vehicle_ends_to_neighbors = false;
|
|
// In NodeNeighborsByCostClass, neighbors for each node are sorted by
|
|
// increasing "cost" for each node. The following parameter determines if
|
|
// neighbors are sorted based on distance only when the neighborhood is
|
|
// partial, i.e. when num_neighbors entails that not all nodes are
|
|
// neighbors.
|
|
bool only_sort_neighbors_for_partial_neighborhoods = true;
|
|
|
|
bool operator==(const NodeNeighborsParameters& other) const {
|
|
return num_neighbors == other.num_neighbors &&
|
|
add_vehicle_starts_to_neighbors ==
|
|
other.add_vehicle_starts_to_neighbors &&
|
|
add_vehicle_ends_to_neighbors ==
|
|
other.add_vehicle_ends_to_neighbors &&
|
|
only_sort_neighbors_for_partial_neighborhoods ==
|
|
other.only_sort_neighbors_for_partial_neighborhoods;
|
|
}
|
|
template <typename H>
|
|
friend H AbslHashValue(H h, const NodeNeighborsParameters& params) {
|
|
return H::combine(std::move(h), params.num_neighbors,
|
|
params.add_vehicle_starts_to_neighbors,
|
|
params.add_vehicle_ends_to_neighbors,
|
|
params.only_sort_neighbors_for_partial_neighborhoods);
|
|
}
|
|
};
|
|
class NodeNeighborsByCostClass {
|
|
public:
|
|
explicit NodeNeighborsByCostClass(const RoutingModel* routing_model)
|
|
: routing_model_(*routing_model) {};
|
|
|
|
/// Computes num_neighbors neighbors of all nodes for every cost class in
|
|
/// routing_model.
|
|
void ComputeNeighbors(const NodeNeighborsParameters& params);
|
|
/// Returns the incoming neighbors of the given node for the given
|
|
/// cost_class, i.e. all 'neighbor' indices such that neighbor -> node_index
|
|
/// is a neighborhood arc for 'cost_class'.
|
|
const std::vector<int>& GetIncomingNeighborsOfNodeForCostClass(
|
|
int cost_class, int node_index) const {
|
|
if (routing_model_.IsStart(node_index)) return empty_neighbors_;
|
|
|
|
if (node_index_to_incoming_neighbors_by_cost_class_.empty()) {
|
|
DCHECK(IsFullNeighborhood());
|
|
return all_incoming_nodes_;
|
|
}
|
|
const std::vector<std::vector<int>>& node_index_to_incoming_neighbors =
|
|
node_index_to_incoming_neighbors_by_cost_class_[cost_class];
|
|
if (node_index_to_incoming_neighbors.empty()) {
|
|
return empty_neighbors_;
|
|
}
|
|
return node_index_to_incoming_neighbors[node_index];
|
|
}
|
|
|
|
/// Returns the neighbors that are outgoing from 'node_index', i.e.
|
|
/// 'neighbor' indices such that node_index -> neighbor is a neighborhood
|
|
/// arc for 'cost_class'.
|
|
const std::vector<int>& GetOutgoingNeighborsOfNodeForCostClass(
|
|
int cost_class, int node_index) const {
|
|
if (routing_model_.IsEnd(node_index)) return empty_neighbors_;
|
|
|
|
if (node_index_to_outgoing_neighbors_by_cost_class_.empty()) {
|
|
DCHECK(IsFullNeighborhood());
|
|
return all_outgoing_nodes_;
|
|
}
|
|
const std::vector<std::vector<int>>& node_index_to_outgoing_neighbors =
|
|
node_index_to_outgoing_neighbors_by_cost_class_[cost_class];
|
|
if (node_index_to_outgoing_neighbors.empty()) {
|
|
return empty_neighbors_;
|
|
}
|
|
return node_index_to_outgoing_neighbors[node_index];
|
|
}
|
|
/// Returns true iff arc from_node -> to_node is a neighborhood arc for the
|
|
/// given cost_class, i.e. iff arc.to_node is an outgoing neighbor of
|
|
/// arc.from_node for 'cost_class'.
|
|
bool IsNeighborhoodArcForCostClass(int cost_class, int64_t from,
|
|
int64_t to) const {
|
|
if (node_index_to_outgoing_neighbor_indicator_by_cost_class_.empty()) {
|
|
DCHECK(full_neighborhood_);
|
|
return true;
|
|
}
|
|
if (routing_model_.IsEnd(from)) {
|
|
return false;
|
|
}
|
|
return node_index_to_outgoing_neighbor_indicator_by_cost_class_
|
|
[cost_class][from][to];
|
|
}
|
|
|
|
bool IsFullNeighborhood() const { return full_neighborhood_; }
|
|
|
|
private:
|
|
const RoutingModel& routing_model_;
|
|
#if __cplusplus >= 202002L
|
|
static constexpr std::vector<int> empty_neighbors_ = {};
|
|
#else
|
|
inline static const std::vector<int> empty_neighbors_ = {};
|
|
#endif
|
|
|
|
std::vector<std::vector<std::vector<int>>>
|
|
node_index_to_incoming_neighbors_by_cost_class_;
|
|
std::vector<std::vector<std::vector<int>>>
|
|
node_index_to_outgoing_neighbors_by_cost_class_;
|
|
std::vector<std::vector<std::vector<bool>>>
|
|
node_index_to_outgoing_neighbor_indicator_by_cost_class_;
|
|
|
|
std::vector<int> all_outgoing_nodes_;
|
|
std::vector<int> all_incoming_nodes_;
|
|
|
|
bool full_neighborhood_ = false;
|
|
};
|
|
|
|
/// Returns neighbors of all nodes for every cost class. The result is cached
|
|
/// and is computed once. The number of neighbors considered is based on a
|
|
/// ratio of non-vehicle nodes, specified by neighbors_ratio, with a minimum
|
|
/// of min-neighbors node considered.
|
|
const NodeNeighborsByCostClass* GetOrCreateNodeNeighborsByCostClass(
|
|
double neighbors_ratio, int64_t min_neighbors,
|
|
double& neighbors_ratio_used, bool add_vehicle_starts_to_neighbors = true,
|
|
bool add_vehicle_ends_to_neighbors = false,
|
|
bool only_sort_neighbors_for_partial_neighborhoods = true);
|
|
/// Returns parameters.num_neighbors neighbors of all nodes for every cost
|
|
/// class. The result is cached and is computed once.
|
|
const NodeNeighborsByCostClass* GetOrCreateNodeNeighborsByCostClass(
|
|
const NodeNeighborsParameters& params);
|
|
/// 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 num_vehicle_classes_; }
|
|
/// 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]];
|
|
}
|
|
void AddSameActivityGroup(const std::vector<int>& nodes) {
|
|
DCHECK(!closed_);
|
|
if (nodes.size() <= 1) return;
|
|
for (auto it = nodes.begin() + 1; it != nodes.end(); ++it) {
|
|
solver_->AddConstraint(
|
|
solver_->MakeEquality(ActiveVar(*it), ActiveVar(*(it - 1))));
|
|
}
|
|
}
|
|
/// Returns variable indices of nodes constrained to have the same activity.
|
|
const std::vector<int>& GetSameActivityIndicesOfIndex(int node) const {
|
|
DCHECK(closed_);
|
|
return same_active_var_groups_[same_active_var_group_[node]];
|
|
}
|
|
/// Returns the same activity group of the node.
|
|
int GetSameActivityGroupOfIndex(int node) const {
|
|
DCHECK(closed_);
|
|
return same_active_var_group_[node];
|
|
}
|
|
/// Returns same activity groups of all nodes.
|
|
const std::vector<int>& GetSameActivityGroups() const {
|
|
DCHECK(closed_);
|
|
return same_active_var_group_;
|
|
}
|
|
/// Returns the number of same activity groups.
|
|
int GetSameActivityGroupsCount() const {
|
|
DCHECK(closed_);
|
|
return same_active_var_groups_.size();
|
|
}
|
|
/// Returns variable indices of nodes in the same activity group.
|
|
const std::vector<int>& GetSameActivityIndicesOfGroup(int group) const {
|
|
DCHECK(closed_);
|
|
return same_active_var_groups_[group];
|
|
}
|
|
/// Adds an ordered activity group. This enforces that if nodes[i] is active,
|
|
/// then nodes[i-1] must be active.
|
|
void AddOrderedActivityGroup(std::vector<int> nodes) {
|
|
DCHECK(!closed_);
|
|
if (nodes.size() <= 1) return;
|
|
ordered_activity_groups_.push_back(std::move(nodes));
|
|
}
|
|
#ifndef SWIG
|
|
/// Returns all ordered activity groups.
|
|
const std::vector<std::vector<int>>& GetOrderedActivityGroups() const {
|
|
return ordered_activity_groups_;
|
|
}
|
|
#endif // SWIG
|
|
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
|
|
/// Checks if an assignment is feasible.
|
|
bool CheckIfAssignmentIsFeasible(const Assignment& assignment,
|
|
bool call_at_solution_monitors);
|
|
/// 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 with the given time
|
|
/// offset.
|
|
bool CheckLimit(absl::Duration offset = absl::ZeroDuration()) {
|
|
DCHECK(limit_ != nullptr);
|
|
return limit_->CheckWithOffset(offset);
|
|
}
|
|
|
|
/// Returns the time left in the search limit.
|
|
absl::Duration RemainingTime() const {
|
|
DCHECK(limit_ != nullptr);
|
|
return limit_->AbsoluteSolverDeadline() - solver_->Now();
|
|
}
|
|
|
|
/// Updates the time limit of the search limit.
|
|
void UpdateTimeLimit(absl::Duration time_limit) {
|
|
RegularLimit* limit = GetOrCreateLimit();
|
|
limit->UpdateLimits(time_limit, std::numeric_limits<int64_t>::max(),
|
|
std::numeric_limits<int64_t>::max(),
|
|
limit->solutions());
|
|
}
|
|
|
|
/// Returns the time buffer to safely return a solution.
|
|
absl::Duration TimeBuffer() const { return time_buffer_; }
|
|
|
|
/// Returns the atomic<bool> to stop the CP-SAT solver.
|
|
std::atomic<bool>* GetMutableCPSatInterrupt() { return &interrupt_cp_sat_; }
|
|
/// Returns the atomic<bool> to stop the CP solver.
|
|
std::atomic<bool>* GetMutableCPInterrupt() { return &interrupt_cp_; }
|
|
/// Cancels the current search.
|
|
void CancelSearch() {
|
|
interrupt_cp_sat_ = true;
|
|
interrupt_cp_ = true;
|
|
}
|
|
|
|
/// 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);
|
|
|
|
const PathsMetadata& GetPathsMetadata() const { return paths_metadata_; }
|
|
#ifndef SWIG
|
|
BinCapacities* GetBinCapacities() { return bin_capacities_.get(); }
|
|
|
|
/// Sets a secondary solver (routing model + parameters) which can be used to
|
|
/// run sub-solves while building a first solution.
|
|
void SetSecondaryModel(RoutingModel* secondary_model,
|
|
RoutingSearchParameters secondary_parameters) {
|
|
DCHECK(!closed_);
|
|
secondary_model_ = secondary_model;
|
|
secondary_parameters_ = std::move(secondary_parameters);
|
|
}
|
|
#endif // SWIG
|
|
|
|
/// Returns indices of the vehicles which are in the same vehicle class as the
|
|
/// vehicle starting or ending at start_end_index.
|
|
const std::deque<int>& GetVehiclesOfSameClass(int64_t start_end_index) const;
|
|
|
|
/// Returns all arcs which are equivalent to the {from_index, to_index} arc
|
|
/// wrt vehicle classes. Arcs will be returned only if from_index is the
|
|
/// start of a vehicle or if to_index is the end of a vehicle. The returned
|
|
/// arcs will then be starting or ending at start or end nodes of vehicles in
|
|
/// the same vehicle class. The input arc is included in the returned vector.
|
|
std::vector<std::pair<int64_t, int64_t>> GetSameVehicleClassArcs(
|
|
int64_t from_index, int64_t to_index) const;
|
|
|
|
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_VISIT_TYPES_LNS,
|
|
LOCAL_CHEAPEST_INSERTION_VISIT_TYPES_LNS,
|
|
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,
|
|
EXCHANGE_AND_MAKE_ACTIVE,
|
|
EXCHANGE_PATH_START_ENDS_AND_MAKE_ACTIVE,
|
|
MAKE_INACTIVE,
|
|
MAKE_CHAIN_INACTIVE,
|
|
SWAP_ACTIVE,
|
|
SWAP_ACTIVE_CHAIN,
|
|
EXTENDED_SWAP_ACTIVE,
|
|
SHORTEST_PATH_SWAP_ACTIVE,
|
|
SHORTEST_PATH_TWO_OPT,
|
|
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;
|
|
PenaltyCostBehavior penalty_cost_behavior;
|
|
};
|
|
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,
|
|
const std::vector<int>& cumul_dependent_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>& cumul_dependent_evaluator_indices,
|
|
const std::vector<int>& state_dependent_evaluator_indices,
|
|
int64_t slack_max, bool fix_start_cumul_to_zero,
|
|
RoutingDimension* dimension);
|
|
DimensionIndex GetDimensionIndex(absl::string_view 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);
|
|
|
|
/// The following method looks at node-to-vehicle assignment feasibility
|
|
/// based on node transit values on unary dimensions: If the (absolute) value
|
|
/// of transit for a node is greater than the vehicle capacity on any
|
|
/// dimension, this node cannot be served by this vehicle and the latter is
|
|
/// thus removed from allowed_vehicles_[node].
|
|
void FinalizeAllowedVehicles();
|
|
|
|
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();
|
|
/// Computes resource classes for all resource groups in the model.
|
|
void ComputeResourceClasses();
|
|
/// 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();
|
|
void ComputeVisitTypesConnectedComponents();
|
|
// Called by FinalizeVisitTypes() to setup topologically_sorted_visit_types_.
|
|
void TopologicallySortVisitTypes();
|
|
// This method updates topologically_sorted_node_precedences_ which contains
|
|
// nodes in topological order based on precedence constraints for
|
|
// dimensions of the model.
|
|
void FinalizePrecedences();
|
|
int64_t GetArcCostForClassInternal(int64_t from_index, int64_t to_index,
|
|
CostClassIndex cost_class_index) const;
|
|
int64_t GetArcCostWithGuidedLocalSearchPenalties(int64_t from_index,
|
|
int64_t to_index,
|
|
int64_t vehicle) const {
|
|
return CapAdd(
|
|
GetArcCostForVehicle(from_index, to_index, vehicle),
|
|
solver()->GetGuidedLocalSearchPenalty(from_index, to_index, vehicle));
|
|
}
|
|
std::function<int64_t(int64_t, int64_t, int64_t)>
|
|
GetLocalSearchArcCostCallback(
|
|
const RoutingSearchParameters& parameters) const;
|
|
int64_t GetHomogeneousArcCostWithGuidedLocalSearchPenalties(
|
|
int64_t from_index, int64_t to_index) const {
|
|
return GetArcCostWithGuidedLocalSearchPenalties(from_index, to_index,
|
|
/*vehicle=*/0);
|
|
}
|
|
std::function<int64_t(int64_t, int64_t)>
|
|
GetLocalSearchHomogeneousArcCostCallback(
|
|
const RoutingSearchParameters& parameters) 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, absl::Span<const 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,
|
|
bool call_at_solution_monitors = true);
|
|
#endif
|
|
/// Log a solution.
|
|
void LogSolution(const RoutingSearchParameters& parameters,
|
|
absl::string_view 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.
|
|
std::string FindErrorInSearchParametersForModel(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
/// Sets up search objects, such as decision builders and monitors.
|
|
void SetupSearch(const RoutingSearchParameters& search_parameters);
|
|
/// Updates search objects if parameters have changed.
|
|
void UpdateSearchFromParametersIfNeeded(
|
|
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* GetOrCreateCumulativeLimit();
|
|
RegularLimit* GetOrCreateLocalSearchLimit();
|
|
RegularLimit* GetOrCreateLargeNeighborhoodSearchLimit();
|
|
RegularLimit* GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit();
|
|
LocalSearchOperator* CreateInsertionOperator();
|
|
LocalSearchOperator* CreateMakeInactiveOperator();
|
|
void CreateNeighborhoodOperators(const RoutingSearchParameters& parameters);
|
|
LocalSearchOperator* ConcatenateOperators(
|
|
const RoutingSearchParameters& search_parameters,
|
|
const std::vector<LocalSearchOperator*>& operators) const;
|
|
LocalSearchOperator* GetNeighborhoodOperators(
|
|
const RoutingSearchParameters& search_parameters,
|
|
const absl::flat_hash_set<RoutingLocalSearchOperator>&
|
|
operators_to_consider) 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);
|
|
void CreateFirstSolutionDecisionBuilders(
|
|
const RoutingSearchParameters& search_parameters);
|
|
DecisionBuilder* GetFirstSolutionDecisionBuilder(
|
|
const RoutingSearchParameters& search_parameters) const;
|
|
IntVarFilteredDecisionBuilder* GetFilteredFirstSolutionDecisionBuilderOrNull(
|
|
const RoutingSearchParameters& parameters) const;
|
|
#ifndef SWIG
|
|
template <typename Heuristic, typename... Args>
|
|
IntVarFilteredDecisionBuilder* CreateIntVarFilteredDecisionBuilder(
|
|
const Args&... args);
|
|
#endif
|
|
LocalSearchPhaseParameters* CreateLocalSearchParameters(
|
|
const RoutingSearchParameters& search_parameters, bool secondary_ls);
|
|
DecisionBuilder* CreatePrimaryLocalSearchDecisionBuilder(
|
|
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);
|
|
}
|
|
|
|
void InitSameActiveVarGroups(int number_of_groups) {
|
|
same_active_var_group_.assign(Size(), 0);
|
|
same_active_var_groups_.assign(number_of_groups, {});
|
|
}
|
|
void SetSameActiveVarGroup(int index, int group) {
|
|
same_active_var_group_[index] = group;
|
|
same_active_var_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_;
|
|
util_intops::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.
|
|
util_intops::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_;
|
|
util_intops::StrongVector<DimensionIndex, int> global_optimizer_index_;
|
|
std::vector<DimensionCumulOptimizers<LocalDimensionCumulOptimizer> >
|
|
local_dimension_optimizers_;
|
|
util_intops::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_;
|
|
mutable std::vector<
|
|
absl::AnyInvocable<std::optional<int64_t>(const std::vector<int64_t>&)>>
|
|
route_evaluators_;
|
|
/// 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::flat_hash_map<
|
|
std::pair<std::string, std::string>,
|
|
std::vector<Solver::PathEnergyCostConstraintSpecification::EnergyCost>,
|
|
absl::Hash<std::pair<std::string, std::string>>>
|
|
force_distance_to_energy_costs_;
|
|
util_intops::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_;
|
|
int num_vehicle_classes_;
|
|
|
|
VehicleTypeContainer vehicle_type_container_;
|
|
std::function<int(int64_t)> vehicle_start_class_callback_;
|
|
/// Disjunctions
|
|
util_intops::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
|
|
std::vector<PickupDeliveryPair> pickup_delivery_pairs_;
|
|
std::vector<PickupDeliveryPair>
|
|
implicit_pickup_delivery_pairs_without_alternatives_;
|
|
std::vector<std::pair<DisjunctionIndex, DisjunctionIndex> >
|
|
pickup_delivery_disjunctions_;
|
|
// If node_index is a pickup, index_to_pickup_position_[node_index] contains
|
|
// the PickupDeliveryPosition {pickup_delivery_index, alternative_index}
|
|
// such that (pickup_delivery_pairs_[pickup_delivery_index]
|
|
// .pickup_alternatives)[alternative_index] == node_index
|
|
std::vector<PickupDeliveryPosition> index_to_pickup_position_;
|
|
// Same as above for deliveries.
|
|
std::vector<PickupDeliveryPosition> index_to_delivery_position_;
|
|
// 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_;
|
|
// Same active var group to which a node belongs.
|
|
std::vector<int> same_active_var_group_;
|
|
// Same active var groups.
|
|
std::vector<std::vector<int>> same_active_var_groups_;
|
|
// Ordered activity groups.
|
|
std::vector<std::vector<int>> ordered_activity_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_;
|
|
std::vector<absl::flat_hash_set<int> >
|
|
temporal_incompatible_types_per_type_index_;
|
|
const absl::flat_hash_set<int> empty_incompatibility_set_;
|
|
|
|
std::vector<std::vector<absl::flat_hash_set<int> > >
|
|
same_vehicle_required_type_alternatives_per_type_index_;
|
|
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_;
|
|
const std::vector<absl::flat_hash_set<int>> empty_required_type_alternatives_;
|
|
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_;
|
|
std::vector<std::vector<int>> visit_type_components_;
|
|
// clang-format on
|
|
int num_visit_types_;
|
|
std::vector<std::vector<std::vector<int>>>
|
|
topologically_sorted_node_precedences_;
|
|
// 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;
|
|
RoutingSearchStatus::Value status_ = RoutingSearchStatus::ROUTING_NOT_SOLVED;
|
|
bool enable_deep_serialization_ = true;
|
|
|
|
// Secondary routing solver
|
|
RoutingModel* secondary_model_ = nullptr;
|
|
RoutingSearchParameters secondary_parameters_;
|
|
std::unique_ptr<SecondaryOptimizer> secondary_optimizer_;
|
|
|
|
// 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;
|
|
const Assignment* hint_ = nullptr;
|
|
std::vector<LocalSearchOperator*> local_search_operators_;
|
|
std::vector<SearchMonitor*> monitors_;
|
|
std::vector<SearchMonitor*> secondary_ls_monitors_;
|
|
std::vector<SearchMonitor*> at_solution_monitors_;
|
|
std::vector<std::function<void()>> restore_dimension_values_reset_callbacks_;
|
|
int monitors_before_setup_ = 0;
|
|
int monitors_after_setup_ = 0;
|
|
SearchMonitor* metaheuristic_ = nullptr;
|
|
SearchMonitor* search_log_ = nullptr;
|
|
bool local_optimum_reached_ = false;
|
|
// Best lower bound found during the search.
|
|
int64_t objective_lower_bound_ = kint64min;
|
|
SolutionCollector* collect_assignments_ = nullptr;
|
|
SolutionCollector* collect_secondary_ls_assignments_ = nullptr;
|
|
SolutionCollector* collect_one_assignment_ = nullptr;
|
|
SolutionCollector* optimized_dimensions_assignment_collector_ = nullptr;
|
|
RoutingSearchParameters search_parameters_;
|
|
DecisionBuilder* solve_db_ = nullptr;
|
|
DecisionBuilder* improve_db_ = nullptr;
|
|
DecisionBuilder* secondary_ls_db_ = nullptr;
|
|
DecisionBuilder* restore_assignment_ = nullptr;
|
|
DecisionBuilder* restore_tmp_assignment_ = nullptr;
|
|
Assignment* assignment_ = nullptr;
|
|
Assignment* preassignment_ = nullptr;
|
|
Assignment* tmp_assignment_ = nullptr;
|
|
LocalSearchOperator* primary_ls_operator_ = nullptr;
|
|
LocalSearchOperator* secondary_ls_operator_ = 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_;
|
|
absl::flat_hash_map<NodeNeighborsParameters,
|
|
std::unique_ptr<NodeNeighborsByCostClass>>
|
|
node_neighbors_by_cost_class_per_size_;
|
|
std::unique_ptr<FinalizerVariables> finalizer_variables_;
|
|
#ifndef SWIG
|
|
std::unique_ptr<SweepArranger> sweep_arranger_;
|
|
#endif
|
|
|
|
RegularLimit* limit_ = nullptr;
|
|
RegularLimit* cumulative_limit_ = nullptr;
|
|
RegularLimit* ls_limit_ = nullptr;
|
|
RegularLimit* lns_limit_ = nullptr;
|
|
RegularLimit* first_solution_lns_limit_ = nullptr;
|
|
absl::Duration time_buffer_;
|
|
|
|
RoutingSearchStats search_stats_;
|
|
|
|
std::atomic<bool> interrupt_cp_sat_;
|
|
std::atomic<bool> interrupt_cp_;
|
|
|
|
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;
|
|
|
|
// All transit callbacks are stored in transit_evaluators_,
|
|
// we refer to callbacks by the index in this vector.
|
|
// We maintain unary_transit_evaluators_[] (with the same size) to store
|
|
// callbacks that are unary:
|
|
// - if a callback is unary, it is in unary_transit_evaluators_[i],
|
|
// and a binary version is stored at transit_evaluators_[i].
|
|
// - if a callback is binary, it is stored at transit_evaluators_[i],
|
|
// and unary_transit_evaluators_[i] is nullptr.
|
|
std::vector<TransitCallback1> unary_transit_evaluators_;
|
|
std::vector<TransitCallback2> transit_evaluators_;
|
|
std::vector<TransitEvaluatorSign> transit_evaluator_sign_;
|
|
|
|
std::vector<VariableIndexEvaluator2> state_dependent_transit_evaluators_;
|
|
std::vector<std::unique_ptr<StateDependentTransitCallbackCache>>
|
|
state_dependent_transit_evaluators_cache_;
|
|
|
|
std::vector<CumulDependentTransitCallback2>
|
|
cumul_dependent_transit_evaluators_;
|
|
|
|
// Returns global BinCapacities state, may be nullptr.
|
|
std::unique_ptr<BinCapacities> bin_capacities_;
|
|
|
|
friend class RoutingDimension;
|
|
friend class RoutingModelInspector;
|
|
friend class ResourceGroup::Resource;
|
|
};
|
|
|
|
/// Routing model visitor.
|
|
class OR_DLL RoutingModelVisitor : public BaseObject {
|
|
public:
|
|
/// Constraint types.
|
|
static const char kLightElement[];
|
|
static const char kLightElement2[];
|
|
static const char kRemoveValues[];
|
|
};
|
|
|
|
#if !defined(SWIG)
|
|
void FillPathEvaluation(absl::Span<const int64_t> path,
|
|
const RoutingModel::TransitCallback2& evaluator,
|
|
std::vector<int64_t>* values);
|
|
#endif // !defined(SWIG)
|
|
|
|
class TypeRegulationsChecker {
|
|
public:
|
|
explicit TypeRegulationsChecker(const RoutingModel& model);
|
|
virtual ~TypeRegulationsChecker() = default;
|
|
|
|
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 = default;
|
|
|
|
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 = default;
|
|
|
|
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(
|
|
absl::Span<const 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_;
|
|
};
|
|
|
|
/// 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.
|
|
struct BoundCost {
|
|
int64_t bound;
|
|
int64_t cost;
|
|
BoundCost() : bound(0), cost(0) {}
|
|
BoundCost(int64_t bound, int64_t cost) : bound(bound), cost(cost) {}
|
|
};
|
|
|
|
class SimpleBoundCosts {
|
|
public:
|
|
SimpleBoundCosts(int num_bounds, BoundCost default_bound_cost)
|
|
: bound_costs_(num_bounds, default_bound_cost) {}
|
|
#ifndef SWIG
|
|
BoundCost& bound_cost(int element) { return bound_costs_[element]; }
|
|
#endif
|
|
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_;
|
|
};
|
|
|
|
/// 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:
|
|
// This type is neither copyable nor movable.
|
|
RoutingDimension(const RoutingDimension&) = delete;
|
|
RoutingDimension& operator=(const RoutingDimension&) = delete;
|
|
|
|
~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]; }
|
|
|
|
/// Some functions to allow users to use the interface without knowing about
|
|
/// the underlying CP model.
|
|
// TODO(user): Routing should not store its data in a CP model.
|
|
|
|
/// Restricts the range of the cumul variable associated to index.
|
|
void SetCumulVarRange(int64_t index, int64_t min, int64_t max) {
|
|
CumulVar(index)->SetRange(min, max);
|
|
}
|
|
/// Gets the current minimum of the cumul variable associated to index.
|
|
int64_t GetCumulVarMin(int64_t index) const { return CumulVar(index)->Min(); }
|
|
/// Gets the current maximum of the cumul variable associated to index.
|
|
int64_t GetCumulVarMax(int64_t index) const { return CumulVar(index)->Max(); }
|
|
|
|
#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 true iff all transit evaluators for this dimension are unary.
|
|
bool IsUnary() const {
|
|
for (int evaluator_index : class_evaluators_) {
|
|
if (model_->UnaryTransitCallbackOrNull(evaluator_index) == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// 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]]);
|
|
}
|
|
const RoutingModel::TransitCallback2& GetBinaryTransitEvaluator(
|
|
int vehicle) const {
|
|
return model_->TransitCallback(
|
|
class_evaluators_[vehicle_to_class_[vehicle]]);
|
|
}
|
|
/// Returns true iff the transit evaluator of 'vehicle' is positive for all
|
|
/// arcs.
|
|
bool AreVehicleTransitsPositive(int vehicle) const {
|
|
const int evaluator_index = class_evaluators_[vehicle_to_class_[vehicle]];
|
|
return model()->transit_evaluator_sign_[evaluator_index] ==
|
|
RoutingModel::kTransitEvaluatorSignPositiveOrZero;
|
|
}
|
|
bool AllTransitEvaluatorSignsAreUnknown() const;
|
|
RoutingModel::TransitEvaluatorSign GetTransitEvaluatorSign(
|
|
int vehicle) const {
|
|
const int evaluator_index = class_evaluators_[vehicle_to_class_[vehicle]];
|
|
return model()->transit_evaluator_sign_[evaluator_index];
|
|
}
|
|
int vehicle_to_class(int vehicle) const { return vehicle_to_class_[vehicle]; }
|
|
int vehicle_to_cumul_dependent_class(int vehicle) const {
|
|
if (vehicle_to_cumul_dependent_class_.empty()) {
|
|
return -1;
|
|
}
|
|
DCHECK_LT(vehicle, vehicle_to_cumul_dependent_class_.size());
|
|
return vehicle_to_cumul_dependent_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 dimension total slack on a given vehicle,
|
|
/// or on all vehicles at once. "coefficient" must be nonnegative.
|
|
/// This is handy to model costs only proportional to idle time when the
|
|
/// dimension represents time.
|
|
/// The cost for a vehicle is
|
|
/// slack_cost = coefficient *
|
|
/// (dimension end value - dimension start value - total_transit).
|
|
void SetSlackCostCoefficientForVehicle(int64_t coefficient, int vehicle);
|
|
void SetSlackCostCoefficientForAllVehicles(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.swig 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_alternative_index,
|
|
int delivery_alternative_index) const;
|
|
|
|
struct NodePrecedence {
|
|
int64_t first_node;
|
|
int64_t second_node;
|
|
int64_t offset;
|
|
enum class PerformedConstraint {
|
|
// first_node and/or second_node can be unperformed.
|
|
kFirstAndSecondIndependent,
|
|
// if second_node is performed, first_node must be performed.
|
|
kSecondImpliesFirst,
|
|
// if first_node is performed, second_node must be performed.
|
|
kFirstImpliesSecond,
|
|
// first_node is performed iff second_node is performed.
|
|
kFirstAndSecondEqual,
|
|
};
|
|
PerformedConstraint performed_constraint;
|
|
};
|
|
|
|
void AddNodePrecedence(NodePrecedence precedence) {
|
|
node_precedences_.push_back(precedence);
|
|
}
|
|
const std::vector<NodePrecedence>& GetNodePrecedences() const {
|
|
return node_precedences_;
|
|
}
|
|
/// Returns the status of a precedence based on the performed constraint and
|
|
/// the performed status of the first and second node of a precedence.
|
|
enum class PrecedenceStatus {
|
|
kActive,
|
|
kInactive,
|
|
kInvalid,
|
|
};
|
|
static PrecedenceStatus GetPrecedenceStatus(
|
|
bool first_unperformed, bool second_unperformed,
|
|
NodePrecedence::PerformedConstraint performed_constraint) {
|
|
switch (performed_constraint) {
|
|
case NodePrecedence::PerformedConstraint::kFirstAndSecondIndependent:
|
|
if (first_unperformed || second_unperformed) {
|
|
return PrecedenceStatus::kInactive;
|
|
}
|
|
break;
|
|
case NodePrecedence::PerformedConstraint::kSecondImpliesFirst:
|
|
if (first_unperformed) {
|
|
if (!second_unperformed) return PrecedenceStatus::kInvalid;
|
|
return PrecedenceStatus::kInactive;
|
|
}
|
|
if (second_unperformed) return PrecedenceStatus::kInactive;
|
|
break;
|
|
case NodePrecedence::PerformedConstraint::kFirstImpliesSecond:
|
|
if (second_unperformed) {
|
|
if (!first_unperformed) return PrecedenceStatus::kInvalid;
|
|
return PrecedenceStatus::kInactive;
|
|
}
|
|
if (first_unperformed) return PrecedenceStatus::kInactive;
|
|
break;
|
|
case NodePrecedence::PerformedConstraint::kFirstAndSecondEqual:
|
|
if (first_unperformed != second_unperformed) {
|
|
return PrecedenceStatus::kInvalid;
|
|
}
|
|
if (first_unperformed) return PrecedenceStatus::kInactive;
|
|
break;
|
|
}
|
|
return PrecedenceStatus::kActive;
|
|
}
|
|
void AddNodePrecedence(
|
|
int64_t first_node, int64_t second_node, int64_t offset,
|
|
NodePrecedence::PerformedConstraint performed_constraint =
|
|
NodePrecedence::PerformedConstraint::kFirstAndSecondIndependent) {
|
|
AddNodePrecedence({first_node, second_node, offset, performed_constraint});
|
|
}
|
|
#else
|
|
void AddNodePrecedence(int64_t first_node, int64_t second_node,
|
|
int64_t offset) {
|
|
AddNodePrecedence(
|
|
{first_node, second_node, offset,
|
|
NodePrecedence::PerformedConstraint::kFirstAndSecondIndependent});
|
|
}
|
|
#endif // SWIG
|
|
|
|
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
|
|
#ifndef SWIG
|
|
const std::vector<int64_t>& vehicle_slack_cost_coefficients() const {
|
|
return vehicle_slack_cost_coefficients_;
|
|
}
|
|
#endif // SWIG
|
|
int64_t GetSlackCostCoefficientForVehicle(int vehicle) const {
|
|
return vehicle_slack_cost_coefficients_[vehicle];
|
|
}
|
|
#ifndef SWIG
|
|
int64_t GetSlackCostCoefficientForVehicleClass(
|
|
RoutingVehicleClassIndex vehicle_class) const {
|
|
const int vehicle = model_->GetVehicleOfClass(vehicle_class);
|
|
DCHECK_NE(vehicle, -1);
|
|
return GetSlackCostCoefficientForVehicle(vehicle);
|
|
}
|
|
#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 the span of vehicle on this dimension is larger than bound,
|
|
/// the cost will be increased by cost * (span - bound).
|
|
void SetSoftSpanUpperBoundForVehicle(BoundCost bound_cost, int vehicle) {
|
|
if (!HasSoftSpanUpperBounds()) {
|
|
vehicle_soft_span_upper_bound_ = std::make_unique<SimpleBoundCosts>(
|
|
model_->vehicles(), BoundCost{kint64max, 0});
|
|
}
|
|
vehicle_soft_span_upper_bound_->bound_cost(vehicle) = bound_cost;
|
|
}
|
|
bool HasSoftSpanUpperBounds() const {
|
|
return vehicle_soft_span_upper_bound_ != nullptr;
|
|
}
|
|
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(BoundCost bound_cost,
|
|
int vehicle) {
|
|
if (!HasQuadraticCostSoftSpanUpperBounds()) {
|
|
vehicle_quadratic_cost_soft_span_upper_bound_ =
|
|
std::make_unique<SimpleBoundCosts>(model_->vehicles(),
|
|
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;
|
|
}
|
|
BoundCost GetQuadraticCostSoftSpanUpperBoundForVehicle(int vehicle) const {
|
|
DCHECK(HasQuadraticCostSoftSpanUpperBounds());
|
|
return vehicle_quadratic_cost_soft_span_upper_bound_->bound_cost(vehicle);
|
|
}
|
|
|
|
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(absl::Span<const int> transit_evaluators,
|
|
absl::Span<const int> cumul_dependent_transit_evaluators,
|
|
absl::Span<const int> state_dependent_transit_evaluators,
|
|
int64_t slack_max);
|
|
void InitializeCumuls();
|
|
void InitializeTransits(
|
|
absl::Span<const int> transit_evaluators,
|
|
absl::Span<const int> cumul_dependent_transit_evaluators,
|
|
absl::Span<const 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<int> vehicle_to_class_;
|
|
|
|
/// Values in cumul_dependent_class_evaluators_ correspond to the evaluators
|
|
/// in RoutingModel::cumul_dependent_transit_evaluators_ for each vehicle
|
|
/// class.
|
|
std::vector<int> cumul_dependent_class_evaluators_;
|
|
std::vector<int> vehicle_to_cumul_dependent_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<int> 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<int64_t> vehicle_slack_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;
|
|
};
|
|
|
|
/// Attempts to solve the model using the cp-sat solver. As of 5/2019, will
|
|
/// solve the TSP corresponding to the model if it has a single vehicle.
|
|
/// Therefore the resulting solution might not actually be feasible. Will return
|
|
/// false if a solution could not be found.
|
|
bool SolveModelWithSat(RoutingModel* model, RoutingSearchStats* search_stats,
|
|
const RoutingSearchParameters& search_parameters,
|
|
const Assignment* initial_solution,
|
|
Assignment* solution);
|
|
|
|
} // namespace operations_research
|
|
#endif // ORTOOLS_CONSTRAINT_SOLVER_ROUTING_H_
|