955 lines
40 KiB
C++
955 lines
40 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.
|
|
|
|
#ifndef ORTOOLS_CONSTRAINT_SOLVER_ROUTING_NEIGHBORHOODS_H_
|
|
#define ORTOOLS_CONSTRAINT_SOLVER_ROUTING_NEIGHBORHOODS_H_
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/log/check.h"
|
|
#include "absl/types/span.h"
|
|
#include "ortools/constraint_solver/constraint_solver.h"
|
|
#include "ortools/constraint_solver/constraint_solveri.h"
|
|
#include "ortools/constraint_solver/routing_types.h"
|
|
#include "ortools/util/bitset.h"
|
|
|
|
namespace operations_research {
|
|
|
|
/// Relocate neighborhood which moves chains of neighbors.
|
|
/// The operator starts by relocating a node n after a node m, then continues
|
|
/// moving nodes which were after n as long as the "cost" added is less than
|
|
/// the "cost" of the arc (m, n). If the new chain doesn't respect the domain of
|
|
/// next variables, it will try reordering the nodes.
|
|
/// Possible neighbors for path 1 -> A -> B -> C -> D -> E -> 2 (where (1, 2)
|
|
/// are first and last nodes of the path and can therefore not be moved, A must
|
|
/// be performed before B, and A, D and E are located at the same place):
|
|
/// 1 -> A -> C -> [B] -> D -> E -> 2
|
|
/// 1 -> A -> C -> D -> [B] -> E -> 2
|
|
/// 1 -> A -> C -> D -> E -> [B] -> 2
|
|
/// 1 -> A -> B -> D -> [C] -> E -> 2
|
|
/// 1 -> A -> B -> D -> E -> [C] -> 2
|
|
/// 1 -> A -> [D] -> [E] -> B -> C -> 2
|
|
/// 1 -> A -> B -> [D] -> [E] -> C -> 2
|
|
/// 1 -> A -> [E] -> B -> C -> D -> 2
|
|
/// 1 -> A -> B -> [E] -> C -> D -> 2
|
|
/// 1 -> A -> B -> C -> [E] -> D -> 2
|
|
/// This operator is extremely useful to move chains of nodes which are located
|
|
/// at the same place (for instance nodes part of a same stop).
|
|
// TODO(user): Consider merging with standard Relocate in local_search.cc.
|
|
|
|
template <bool ignore_path_vars>
|
|
class MakeRelocateNeighborsOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
MakeRelocateNeighborsOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
~MakeRelocateNeighborsOperator() override = default;
|
|
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override { return "RelocateNeighbors"; }
|
|
|
|
private:
|
|
/// Moves a chain starting after 'before_chain' and ending at 'chain_end'
|
|
/// after node 'destination'. Tries to repair the resulting solution by
|
|
/// checking if the new arc created after 'destination' is compatible with
|
|
/// NextVar domains, and moves the 'destination' down the path if the solution
|
|
/// is inconsistent. Iterates the process on the new arcs created before
|
|
/// the node 'destination' (if destination was moved).
|
|
bool MoveChainAndRepair(int64_t before_chain, int64_t chain_end,
|
|
int64_t destination);
|
|
|
|
/// Moves node after 'before_to_move' down the path until a position is found
|
|
/// where NextVar domains are not violated, if it exists. Stops when reaching
|
|
/// position after 'up_to'.
|
|
/// If the node was not moved (either because the current position does not
|
|
/// violate any domains or because not such position could be found), returns
|
|
/// -1. If the node was moved to a new position before up_to, returns up_to;
|
|
/// if it was moved just after up_to returns the node which was after up_to.
|
|
int64_t Reposition(int64_t before_to_move, int64_t up_to);
|
|
|
|
RoutingTransitCallback2 arc_evaluator_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeRelocateNeighbors(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
|
|
LocalSearchOperator* MakeRelocateNeighbors(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
|
|
// Class used to compute shortest paths on DAGs formed by chains of alternative
|
|
// node sets.
|
|
class ShortestPathOnAlternatives {
|
|
public:
|
|
ShortestPathOnAlternatives(int num_nodes,
|
|
std::vector<std::vector<int64_t>> alternative_sets,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
bool HasAlternatives(int node) const;
|
|
// Returns the shortest path between source and sink, going through the DAG
|
|
// formed by the alternative nodes of the chain. The path omits source and
|
|
// sink.
|
|
absl::Span<const int64_t> GetShortestPath(int64_t source, int64_t sink,
|
|
absl::Span<const int64_t> chain);
|
|
|
|
private:
|
|
RoutingTransitCallback2 arc_evaluator_;
|
|
std::vector<std::vector<int64_t>> alternative_sets_;
|
|
std::vector<int> to_alternative_set_;
|
|
std::vector<int64_t> path_predecessor_;
|
|
std::vector<int64_t> path_;
|
|
std::vector<int64_t> current_values_;
|
|
SparseBitset<int64_t> touched_;
|
|
};
|
|
|
|
// Reverses a sub-chain of a path and then replaces it with the shortest path on
|
|
// the DAG formed by the sequence of its node alternatives. This operator will
|
|
// only reverse chains with at least a node with multiple alternatives.
|
|
template <bool ignore_path_vars>
|
|
class TwoOptWithShortestPathOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
TwoOptWithShortestPathOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::vector<std::vector<int64_t>> alternative_sets,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
~TwoOptWithShortestPathOperator() override = default;
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override { return "TwoOptWithShortestPath"; }
|
|
|
|
protected:
|
|
bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
|
|
// Both base nodes have to be on the same path.
|
|
return true;
|
|
}
|
|
int64_t GetBaseNodeRestartPosition(int base_index) override {
|
|
return (base_index == 0) ? this->StartNode(0) : this->BaseNode(0);
|
|
}
|
|
// Necessary to have proper information about alternatives of current path.
|
|
bool RestartAtPathStartOnSynchronize() override { return true; }
|
|
|
|
private:
|
|
void ResetIncrementalism() override { ResetChainStatus(); }
|
|
void OnNodeInitialization() override { ResetChainStatus(); }
|
|
void ResetChainStatus() {
|
|
chain_status_.start = -1;
|
|
chain_status_.end = -1;
|
|
chain_status_.has_alternatives = false;
|
|
}
|
|
|
|
struct ChainStatus {
|
|
int64_t start = -1;
|
|
int64_t end = -1;
|
|
bool has_alternatives = false;
|
|
};
|
|
ChainStatus chain_status_;
|
|
ShortestPathOnAlternatives shortest_path_manager_;
|
|
std::vector<int64_t> chain_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeTwoOptWithShortestPath(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::vector<std::vector<int64_t>> alternative_sets,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
|
|
// Swaps active nodes from node alternatives in sequence. Considers chains of
|
|
// nodes with alternatives, builds a DAG from the chain, each "layer" of the DAG
|
|
// being composed of the set of alternatives of the node at a given rank in the
|
|
// chain, fully connected to the next layer. A neighbor is built from the
|
|
// shortest path starting from the node before the chain (source), through the
|
|
// DAG to the node following the chain. The path is valued with a given
|
|
// callback.
|
|
// Example:
|
|
// Alternative sets: {1,2} and {3,4}
|
|
// Current path: 0 -> 1 -> 3 -> 5
|
|
// DAG + source and sink: -> 1 ---> 3 --
|
|
// | \ / v
|
|
// 0 X 5
|
|
// | / \ ^
|
|
// -> 2 ---> 4 --
|
|
// Supposing the shortest path from 0 to 5 is 0, 2, 3, 5, the neighbor for the
|
|
// chain will be: 0 -> 2 -> 3 -> 5.
|
|
// TODO(user): Support vehicle-class-dependent arc_evaluators.
|
|
template <bool ignore_path_vars>
|
|
class SwapActiveToShortestPathOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
SwapActiveToShortestPathOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::vector<std::vector<int64_t>> alternative_sets,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
~SwapActiveToShortestPathOperator() override = default;
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override {
|
|
return "SwapActiveToShortestPath";
|
|
}
|
|
|
|
private:
|
|
ShortestPathOnAlternatives shortest_path_manager_;
|
|
std::vector<int64_t> chain_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeSwapActiveToShortestPath(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::vector<std::vector<int64_t>> alternative_sets,
|
|
RoutingTransitCallback2 arc_evaluator);
|
|
|
|
/// Pair-based neighborhood operators, designed to move nodes by pairs (pairs
|
|
/// are static and given). These neighborhoods are very useful for Pickup and
|
|
/// Delivery problems where pickup and delivery nodes must remain on the same
|
|
/// route.
|
|
// TODO(user): Add option to prune neighbors where the order of node pairs
|
|
// is violated (ie precedence between pickup and delivery nodes).
|
|
// TODO(user): Move this to local_search.cc if it's generic enough.
|
|
// TODO(user): Detect pairs automatically by parsing the constraint model;
|
|
// we could then get rid of the pair API in the RoutingModel
|
|
// class.
|
|
|
|
/// Operator which inserts pairs of inactive nodes into a path.
|
|
/// Possible neighbors for the path 1 -> 2 -> 3 with pair (A, B) inactive
|
|
/// (where 1 and 3 are first and last nodes of the path) are:
|
|
/// 1 -> [A] -> [B] -> 2 -> 3
|
|
/// 1 -> [B] -> 2 -> [A] -> 3
|
|
/// 1 -> [A] -> 2 -> [B] -> 3
|
|
/// 1 -> 2 -> [A] -> [B] -> 3
|
|
/// Note that this operator does not explicitly insert the nodes of a pair one
|
|
/// after the other which forbids the following solutions:
|
|
/// 1 -> [B] -> [A] -> 2 -> 3
|
|
/// 1 -> 2 -> [B] -> [A] -> 3
|
|
/// which can only be obtained by inserting A after B.
|
|
template <bool ignore_path_vars>
|
|
class MakePairActiveOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
MakePairActiveOperator(const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~MakePairActiveOperator() override = default;
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override { return "MakePairActive"; }
|
|
|
|
protected:
|
|
bool MakeOneNeighbor() override;
|
|
bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
|
|
/// Both base nodes have to be on the same path since they represent the
|
|
/// nodes after which unactive node pairs will be moved.
|
|
return true;
|
|
}
|
|
|
|
int64_t GetBaseNodeRestartPosition(int base_index) override;
|
|
|
|
/// Required to ensure that after synchronization the operator is in a state
|
|
/// compatible with GetBaseNodeRestartPosition.
|
|
bool RestartAtPathStartOnSynchronize() override { return true; }
|
|
|
|
private:
|
|
void OnNodeInitialization() override;
|
|
int FindNextInactivePair(int pair_index) const;
|
|
bool ContainsActiveNodes(absl::Span<const int64_t> nodes) const;
|
|
|
|
int inactive_pair_;
|
|
int inactive_pair_first_index_;
|
|
int inactive_pair_second_index_;
|
|
const std::vector<PickupDeliveryPair> pairs_;
|
|
};
|
|
|
|
LocalSearchOperator* MakePairActive(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
/// Operator which makes pairs of active nodes inactive.
|
|
template <bool ignore_path_vars>
|
|
class MakePairInactiveOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
MakePairInactiveOperator(const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override { return "MakePairInActive"; }
|
|
};
|
|
|
|
LocalSearchOperator* MakePairInactive(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
/// Operator which moves a pair of nodes to another position where the first
|
|
/// node of the pair must be before the second node on the same path.
|
|
/// Possible neighbors for the path 1 -> A -> B -> 2 -> 3 (where (1, 3) are
|
|
/// first and last nodes of the path and can therefore not be moved, and (A, B)
|
|
/// is a pair of nodes):
|
|
/// 1 -> [A] -> 2 -> [B] -> 3
|
|
/// 1 -> 2 -> [A] -> [B] -> 3
|
|
/// The pair can be moved to another path.
|
|
template <bool ignore_path_vars>
|
|
class PairRelocateOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
PairRelocateOperator(const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~PairRelocateOperator() override = default;
|
|
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override { return "PairRelocateOperator"; }
|
|
|
|
protected:
|
|
bool OnSamePathAsPreviousBase(int64_t base_index) override {
|
|
/// Both destination nodes must be on the same path.
|
|
return base_index == kPairSecondNodeDestination;
|
|
}
|
|
int64_t GetBaseNodeRestartPosition(int base_index) override;
|
|
|
|
bool ConsiderAlternatives(int64_t base_index) const override {
|
|
return base_index == kPairFirstNode;
|
|
}
|
|
|
|
private:
|
|
bool RestartAtPathStartOnSynchronize() override { return true; }
|
|
|
|
static constexpr int kPairFirstNode = 0;
|
|
static constexpr int kPairFirstNodeDestination = 1;
|
|
static constexpr int kPairSecondNodeDestination = 2;
|
|
};
|
|
|
|
LocalSearchOperator* MakePairRelocate(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
/// Operator which moves a pair of nodes to another position where the first
|
|
/// node of the pair is directly before the second node.
|
|
template <bool ignore_path_vars>
|
|
class GroupPairAndRelocateOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
GroupPairAndRelocateOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~GroupPairAndRelocateOperator() override = default;
|
|
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override { return "GroupPairAndRelocate"; }
|
|
};
|
|
|
|
LocalSearchOperator* MakeGroupPairAndRelocate(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
LocalSearchOperator* MakeGroupPairAndRelocate(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
/// Operator which moves a pair of nodes to another position where the first
|
|
/// node of the pair must be before the second node on the same path.
|
|
/// The default behavior of the operator is to insert the first node after the
|
|
/// first node of another pair, and the second node after the other pair's
|
|
/// second node. This results in a FIFO behavior. The behavior can be overridden
|
|
/// for each path to enforce a LIFO behavior (the second node gets inserted
|
|
/// before the other pair's second node). This is specified using the force_lifo
|
|
/// callback which takes the start node of a path as argument; if the callback
|
|
/// returns true then the LIFO behavior will be enforced, otherwise it's FIFO.
|
|
// TODO(user): Add a version which inserts the first node before the other
|
|
// pair's first node; there are many redundant neighbors if done blindly.
|
|
template <bool ignore_path_vars>
|
|
class LightPairRelocateOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
LightPairRelocateOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
const std::vector<PickupDeliveryPair>& pairs,
|
|
std::function<bool(int64_t)> force_lifo = nullptr);
|
|
~LightPairRelocateOperator() override = default;
|
|
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override {
|
|
return "LightPairRelocateOperator";
|
|
}
|
|
|
|
private:
|
|
std::function<bool(int64_t)> force_lifo_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeLightPairRelocate(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
const std::vector<PickupDeliveryPair>& pairs,
|
|
std::function<bool(int64_t)> force_lifo = nullptr);
|
|
|
|
LocalSearchOperator* MakeLightPairRelocate(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs,
|
|
std::function<bool(int64_t)> force_lifo = nullptr);
|
|
|
|
/// Operator which exchanges the position of two pairs; for both pairs the first
|
|
/// node of the pair must be before the second node on the same path.
|
|
/// Possible neighbors for the paths 1 -> A -> B -> 2 -> 3 and 4 -> C -> D -> 5
|
|
/// (where (1, 3) and (4, 5) are first and last nodes of the paths and can
|
|
/// therefore not be moved, and (A, B) and (C,D) are pairs of nodes):
|
|
/// 1 -> [C] -> [D] -> 2 -> 3, 4 -> [A] -> [B] -> 5
|
|
template <bool ignore_path_vars>
|
|
class PairExchangeOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
PairExchangeOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~PairExchangeOperator() override = default;
|
|
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override { return "PairExchangeOperator"; }
|
|
|
|
private:
|
|
bool RestartAtPathStartOnSynchronize() override { return true; }
|
|
bool ConsiderAlternatives(int64_t /*base_index*/) const override {
|
|
return true;
|
|
}
|
|
bool GetPreviousAndSibling(int64_t node, int64_t* previous, int64_t* sibling,
|
|
int64_t* sibling_previous) const;
|
|
};
|
|
|
|
LocalSearchOperator* MakePairExchange(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
LocalSearchOperator* MakePairExchange(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
/// Operator which exchanges the paths of two pairs (path have to be different).
|
|
/// Pairs are inserted in all possible positions in their new path with the
|
|
/// constraint that the second node must be placed after the first.
|
|
/// Possible neighbors for the path 1 -> A -> B -> 2 -> 3, 4 -> C -> 5 -> D -> 6
|
|
/// 1 -> C -> D -> 2 -> 3 4 -> A -> B -> 5 -> 6
|
|
/// 1 -> C -> 2 -> D -> 3 4 -> A -> 5 -> B -> 6
|
|
/// 1 -> 2 -> C -> D -> 3 4 -> 5 -> A -> B -> 6
|
|
/// 1 -> C -> D -> 2 -> 3 4 -> A -> B -> 5 -> 6
|
|
/// 1 -> C -> 2 -> D -> 3 4 -> A -> 5 -> B -> 6
|
|
/// 1 -> 2 -> C -> D -> 3 4 -> 5 -> A -> B -> 6
|
|
/// 1 -> C -> D -> 2 -> 3 4 -> A -> B -> 5 -> 6
|
|
/// 1 -> C -> 2 -> D -> 3 4 -> A -> 5 -> B -> 6
|
|
/// 1 -> 2 -> C -> D -> 3 4 -> 5 -> A -> B -> 6
|
|
template <bool ignore_path_vars>
|
|
class PairExchangeRelocateOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
PairExchangeRelocateOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~PairExchangeRelocateOperator() override = default;
|
|
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override {
|
|
return "PairExchangeRelocateOperator";
|
|
}
|
|
|
|
protected:
|
|
bool OnSamePathAsPreviousBase(int64_t base_index) override;
|
|
int64_t GetBaseNodeRestartPosition(int base_index) override;
|
|
|
|
private:
|
|
bool RestartAtPathStartOnSynchronize() override { return true; }
|
|
bool GetPreviousAndSibling(int64_t node, int64_t* previous, int64_t* sibling,
|
|
int64_t* sibling_previous) const;
|
|
bool MoveNode(int pair, int node, int64_t nodes[2][2], int64_t dest[2][2],
|
|
int64_t prev[2][2]);
|
|
bool LoadAndCheckDest(int pair, int node, int64_t base_node,
|
|
int64_t nodes[2][2], int64_t dest[2][2]) const;
|
|
|
|
static constexpr int kFirstPairFirstNode = 0;
|
|
static constexpr int kSecondPairFirstNode = 1;
|
|
static constexpr int kFirstPairFirstNodeDestination = 2;
|
|
static constexpr int kFirstPairSecondNodeDestination = 3;
|
|
static constexpr int kSecondPairFirstNodeDestination = 4;
|
|
static constexpr int kSecondPairSecondNodeDestination = 5;
|
|
};
|
|
|
|
LocalSearchOperator* MakePairExchangeRelocate(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
/// Operator which iterates through each alternative of a set of pairs. If a
|
|
/// pair has n and m alternatives, n.m alternatives will be explored.
|
|
/// Possible neighbors for the path 1 -> A -> a -> 2 (where (1, 2) are first and
|
|
/// last nodes of a path and A has B, C as alternatives and a has b as
|
|
/// alternative):
|
|
/// 1 -> A -> [b] -> 2
|
|
/// 1 -> [B] -> a -> 2
|
|
/// 1 -> [B] -> [b] -> 2
|
|
/// 1 -> [C] -> a -> 2
|
|
/// 1 -> [C] -> [b] -> 2
|
|
class SwapIndexPairOperator : public IntVarLocalSearchOperator {
|
|
public:
|
|
SwapIndexPairOperator(const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& path_vars,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~SwapIndexPairOperator() override = default;
|
|
|
|
bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
|
|
void OnStart() override;
|
|
std::string DebugString() const override { return "SwapIndexPairOperator"; }
|
|
|
|
private:
|
|
/// Updates first_active_ and second_active_ to make them correspond to the
|
|
/// active nodes of the node pair of index pair_index_.
|
|
bool UpdateActiveNodes();
|
|
/// Sets the to to be the node after from
|
|
void SetNext(int64_t from, int64_t to, int64_t path) {
|
|
DCHECK_LT(from, number_of_nexts_);
|
|
SetValue(from, to);
|
|
if (!ignore_path_vars_) {
|
|
DCHECK_LT(from + number_of_nexts_, Size());
|
|
SetValue(from + number_of_nexts_, path);
|
|
}
|
|
}
|
|
|
|
const std::vector<PickupDeliveryPair> pairs_;
|
|
int pair_index_;
|
|
int first_index_;
|
|
int second_index_;
|
|
int64_t first_active_;
|
|
int64_t second_active_;
|
|
std::vector<int64_t> prevs_;
|
|
const int number_of_nexts_;
|
|
const bool ignore_path_vars_;
|
|
};
|
|
|
|
/// Operator which inserts inactive nodes into a path and makes a pair of
|
|
/// active nodes inactive.
|
|
template <bool ignore_path_vars>
|
|
class IndexPairSwapActiveOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
IndexPairSwapActiveOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~IndexPairSwapActiveOperator() override = default;
|
|
|
|
bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override {
|
|
return "IndexPairSwapActiveOperator";
|
|
}
|
|
|
|
private:
|
|
void OnNodeInitialization() override;
|
|
|
|
int inactive_node_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeIndexPairSwapActive(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
|
|
/// RelocateExpensiveChain
|
|
///
|
|
/// Operator which relocates the most expensive subchains (given a cost
|
|
/// callback) in a path to a different position.
|
|
///
|
|
/// The most expensive chain on a path is the one resulting from cutting the 2
|
|
/// most expensive arcs on this path.
|
|
template <bool ignore_path_vars>
|
|
class RelocateExpensiveChain : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
RelocateExpensiveChain(const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
int num_arcs_to_consider,
|
|
std::function<int64_t(int64_t, int64_t, int64_t)>
|
|
arc_cost_for_path_start);
|
|
~RelocateExpensiveChain() override = default;
|
|
|
|
bool MakeNeighbor() override;
|
|
bool MakeOneNeighbor() override;
|
|
|
|
std::string DebugString() const override { return "RelocateExpensiveChain"; }
|
|
|
|
private:
|
|
void OnNodeInitialization() override;
|
|
void IncrementCurrentPath();
|
|
bool IncrementCurrentArcIndices();
|
|
/// Tries to find most expensive chains on remaining paths, starting with the
|
|
/// current one, until succeeding on one of them.
|
|
/// Returns false iff all remaining paths are empty.
|
|
bool FindMostExpensiveChainsOnRemainingPaths();
|
|
|
|
int num_arcs_to_consider_;
|
|
int current_path_;
|
|
std::vector<std::pair<int64_t, int>> most_expensive_arc_starts_and_ranks_;
|
|
/// Indices in most_expensive_arc_starts_and_ranks_ corresponding to the first
|
|
/// and second arcs currently being considered for removal.
|
|
std::pair</*first_arc_index*/ int, /*second_arc_index*/ int>
|
|
current_expensive_arc_indices_;
|
|
std::function<int64_t(/*before_node*/ int64_t, /*after_node*/ int64_t,
|
|
/*path_start*/ int64_t)>
|
|
arc_cost_for_path_start_;
|
|
int end_path_;
|
|
/// The following boolean indicates if there are any non-empty paths left to
|
|
/// explore by the operator.
|
|
bool has_non_empty_paths_to_explore_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeRelocateExpensiveChain(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
int num_arcs_to_consider,
|
|
std::function<int64_t(int64_t, int64_t, int64_t)> arc_cost_for_path_start);
|
|
|
|
/// Operator which inserts pairs of inactive nodes into a path and makes an
|
|
/// active node inactive.
|
|
/// There are two versions:
|
|
/// - one which makes inactive the node being replaced by the first node of the
|
|
/// pair (with swap_first true);
|
|
/// - one which makes inactive the node being replaced by the second node of the
|
|
/// pair (with swap_first false).
|
|
template <bool swap_first, bool ignore_path_vars>
|
|
class PairNodeSwapActiveOperator : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
PairNodeSwapActiveOperator(const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs);
|
|
~PairNodeSwapActiveOperator() override = default;
|
|
|
|
bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
|
|
bool MakeNeighbor() override;
|
|
std::string DebugString() const override {
|
|
return "PairNodeSwapActiveOperator";
|
|
}
|
|
|
|
protected:
|
|
bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
|
|
/// Both base nodes have to be on the same path since they represent the
|
|
/// nodes after which unactive node pairs will be moved.
|
|
return true;
|
|
}
|
|
|
|
int64_t GetBaseNodeRestartPosition(int base_index) override;
|
|
|
|
/// Required to ensure that after synchronization the operator is in a state
|
|
/// compatible with GetBaseNodeRestartPosition.
|
|
bool RestartAtPathStartOnSynchronize() override { return true; }
|
|
|
|
private:
|
|
void OnNodeInitialization() override;
|
|
|
|
int inactive_pair_;
|
|
std::vector<PickupDeliveryPair> pairs_;
|
|
};
|
|
|
|
// ==========================================================================
|
|
// Section: Implementations of the template classes declared above.
|
|
|
|
template <bool swap_first, bool ignore_path_vars>
|
|
PairNodeSwapActiveOperator<swap_first, ignore_path_vars>::
|
|
PairNodeSwapActiveOperator(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs)
|
|
: PathOperator<ignore_path_vars>(vars, secondary_vars, 2, false, false,
|
|
std::move(start_empty_path_class), nullptr,
|
|
nullptr),
|
|
inactive_pair_(0),
|
|
pairs_(pairs) {}
|
|
|
|
template <bool swap_first, bool ignore_path_vars>
|
|
int64_t PairNodeSwapActiveOperator<
|
|
swap_first, ignore_path_vars>::GetBaseNodeRestartPosition(int base_index) {
|
|
// Base node 1 must be after base node 0 if they are both on the same path.
|
|
if (base_index == 0 ||
|
|
this->StartNode(base_index) != this->StartNode(base_index - 1)) {
|
|
return this->StartNode(base_index);
|
|
} else {
|
|
return this->BaseNode(base_index - 1);
|
|
}
|
|
}
|
|
|
|
template <bool swap_first, bool ignore_path_vars>
|
|
void PairNodeSwapActiveOperator<swap_first,
|
|
ignore_path_vars>::OnNodeInitialization() {
|
|
for (int i = 0; i < pairs_.size(); ++i) {
|
|
if (this->IsInactive(pairs_[i].pickup_alternatives[0]) &&
|
|
this->IsInactive(pairs_[i].delivery_alternatives[0])) {
|
|
inactive_pair_ = i;
|
|
return;
|
|
}
|
|
}
|
|
inactive_pair_ = pairs_.size();
|
|
}
|
|
|
|
template <bool swap_first, bool ignore_path_vars>
|
|
bool PairNodeSwapActiveOperator<swap_first, ignore_path_vars>::MakeNextNeighbor(
|
|
Assignment* delta, Assignment* deltadelta) {
|
|
while (inactive_pair_ < pairs_.size()) {
|
|
if (!this->IsInactive(pairs_[inactive_pair_].pickup_alternatives[0]) ||
|
|
!this->IsInactive(pairs_[inactive_pair_].delivery_alternatives[0]) ||
|
|
!PathOperator<ignore_path_vars>::MakeNextNeighbor(delta, deltadelta)) {
|
|
this->ResetPosition();
|
|
++inactive_pair_;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <bool swap_first, bool ignore_path_vars>
|
|
bool PairNodeSwapActiveOperator<swap_first, ignore_path_vars>::MakeNeighbor() {
|
|
const int64_t base = this->BaseNode(0);
|
|
if (this->IsPathEnd(base)) {
|
|
return false;
|
|
}
|
|
const int64_t pair_first = pairs_[inactive_pair_].pickup_alternatives[0];
|
|
const int64_t pair_second = pairs_[inactive_pair_].delivery_alternatives[0];
|
|
if (swap_first) {
|
|
return this->MakeActive(pair_second, this->BaseNode(1)) &&
|
|
this->MakeActive(pair_first, base) &&
|
|
this->MakeChainInactive(pair_first, this->Next(pair_first));
|
|
} else {
|
|
return this->MakeActive(pair_second, this->BaseNode(1)) &&
|
|
this->MakeActive(pair_first, base) &&
|
|
this->MakeChainInactive(pair_second, this->Next(pair_second));
|
|
}
|
|
}
|
|
|
|
template <bool swap_first>
|
|
LocalSearchOperator* MakePairNodeSwapActive(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
const std::vector<PickupDeliveryPair>& pairs) {
|
|
if (secondary_vars.empty()) {
|
|
return solver->RevAlloc(new PairNodeSwapActiveOperator<swap_first, true>(
|
|
vars, secondary_vars, std::move(start_empty_path_class), pairs));
|
|
}
|
|
return solver->RevAlloc(new PairNodeSwapActiveOperator<swap_first, false>(
|
|
vars, secondary_vars, std::move(start_empty_path_class), pairs));
|
|
}
|
|
|
|
/// A utility class to maintain pickup and delivery information of nodes.
|
|
class PickupAndDeliveryData {
|
|
public:
|
|
PickupAndDeliveryData(int num_nodes,
|
|
absl::Span<const PickupDeliveryPair> pairs);
|
|
bool IsPickupNode(int64_t node) const {
|
|
DCHECK_LT(node, is_pickup_node_.size());
|
|
return is_pickup_node_[node];
|
|
}
|
|
bool IsDeliveryNode(int64_t node) const {
|
|
DCHECK_LT(node, is_delivery_node_.size());
|
|
return is_delivery_node_[node];
|
|
}
|
|
int GetPairOfNode(int64_t node) const {
|
|
DCHECK_LT(node, pair_of_node_.size());
|
|
return pair_of_node_[node];
|
|
}
|
|
|
|
private:
|
|
std::vector<bool> is_pickup_node_;
|
|
std::vector<bool> is_delivery_node_;
|
|
std::vector<int> pair_of_node_;
|
|
};
|
|
|
|
/// Tries to move subtrips after an insertion node.
|
|
/// A subtrip is a subsequence that contains only matched pickup and delivery
|
|
/// nodes, or pickup-only nodes, i.e. it cannot contain a pickup without a
|
|
/// corresponding delivery or vice-versa.
|
|
///
|
|
/// For a given subtrip given by path indices i_1 ... i_k, we call 'rejected'
|
|
/// the nodes with indices i_1 < j < i_k that are not in the subtrip. If the
|
|
/// base_node is a pickup, this operator selects the smallest subtrip starting
|
|
/// at base_node such that rejected nodes are only deliveries. If the base_node
|
|
/// is a delivery, it selects the smallest subtrip ending at base_node such that
|
|
/// rejected nodes are only pickups.
|
|
template <bool ignore_path_vars>
|
|
class RelocateSubtrip : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
RelocateSubtrip(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
absl::Span<const PickupDeliveryPair> pairs);
|
|
|
|
std::string DebugString() const override { return "RelocateSubtrip"; }
|
|
bool MakeNeighbor() override;
|
|
|
|
private:
|
|
// Relocates the subtrip starting at chain_first_node. It must be a pickup.
|
|
bool RelocateSubTripFromPickup(int64_t chain_first_node,
|
|
int64_t insertion_node);
|
|
/// Relocates the subtrip ending at chain_first_node. It must be a delivery.
|
|
bool RelocateSubTripFromDelivery(int64_t chain_last_node,
|
|
int64_t insertion_node);
|
|
void SetPath(absl::Span<const int64_t> path, int path_id);
|
|
|
|
const PickupAndDeliveryData pd_data_;
|
|
// Represents the set of pairs that have been opened during a call to
|
|
// MakeNeighbor(). This vector must be all false before and after calling
|
|
// RelocateSubTripFromPickup() and RelocateSubTripFromDelivery().
|
|
std::vector<bool> opened_pairs_set_;
|
|
|
|
std::vector<int64_t> rejected_nodes_;
|
|
std::vector<int64_t> subtrip_nodes_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeRelocateSubtrip(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
absl::Span<const PickupDeliveryPair> pairs);
|
|
|
|
LocalSearchOperator* MakeRelocateSubtrip(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
absl::Span<const PickupDeliveryPair> pairs);
|
|
|
|
template <bool ignore_path_vars>
|
|
class ExchangeSubtrip : public PathOperator<ignore_path_vars> {
|
|
public:
|
|
ExchangeSubtrip(
|
|
const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
absl::Span<const PickupDeliveryPair> pairs);
|
|
|
|
std::string DebugString() const override { return "ExchangeSubtrip"; }
|
|
bool MakeNeighbor() override;
|
|
|
|
private:
|
|
// Try to extract a subtrip from base_node (see below) and check that the move
|
|
// will be canonical.
|
|
// Given a pickup/delivery pair, this operator could generate the same move
|
|
// twice, the first time with base_node == pickup, the second time with
|
|
// base_node == delivery. This happens only when no nodes in the subtrip
|
|
// remain in the original path, i.e. when rejects is empty after
|
|
// chain extraction. In that case, we keep only a canonical move out of the
|
|
// two possibilities, the move where base_node is a pickup.
|
|
bool ExtractChainsAndCheckCanonical(int64_t base_node,
|
|
std::vector<int64_t>* rejects,
|
|
std::vector<int64_t>* subtrip);
|
|
// Reads the path from base_node forward, collecting subtrip nodes in
|
|
// subtrip and non-subtrip nodes in rejects.
|
|
// Non-subtrip nodes will be unmatched delivery nodes.
|
|
// base_node must be a pickup, and remaining/extracted_nodes must be empty.
|
|
// Returns true if such chains could be extracted.
|
|
bool ExtractChainsFromPickup(int64_t base_node, std::vector<int64_t>* rejects,
|
|
std::vector<int64_t>* subtrip);
|
|
// Reads the path from base_node backward, collecting subtrip nodes in
|
|
// subtrip and non-subtrip nodes in rejects.
|
|
// Non-subtrip nodes will be unmatched pickup nodes.
|
|
// base_node must be a delivery, and remaining/extracted_nodes must be empty.
|
|
// Returns true if such chains could be extracted.
|
|
bool ExtractChainsFromDelivery(int64_t base_node,
|
|
std::vector<int64_t>* rejects,
|
|
std::vector<int64_t>* subtrip);
|
|
void SetPath(absl::Span<const int64_t> path, int path_id);
|
|
|
|
const PickupAndDeliveryData pd_data_;
|
|
// Represents the set of opened pairs during ExtractChainsFromXXX().
|
|
std::vector<bool> opened_pairs_set_;
|
|
// Keep internal structures under hand to avoid reallocation.
|
|
std::vector<int64_t> rejects0_;
|
|
std::vector<int64_t> subtrip0_;
|
|
std::vector<int64_t> rejects1_;
|
|
std::vector<int64_t> subtrip1_;
|
|
std::vector<int64_t> path0_;
|
|
std::vector<int64_t> path1_;
|
|
};
|
|
|
|
LocalSearchOperator* MakeExchangeSubtrip(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
|
|
std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
|
|
absl::Span<const PickupDeliveryPair> pairs);
|
|
|
|
LocalSearchOperator* MakeExchangeSubtrip(
|
|
Solver* solver, const std::vector<IntVar*>& vars,
|
|
const std::vector<IntVar*>& secondary_vars,
|
|
std::function<int(int64_t)> start_empty_path_class,
|
|
absl::Span<const PickupDeliveryPair> pairs);
|
|
|
|
} // namespace operations_research
|
|
|
|
#endif // ORTOOLS_CONSTRAINT_SOLVER_ROUTING_NEIGHBORHOODS_H_
|