Skip to content

Commit

Permalink
routing: export from google3
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizux committed Aug 14, 2024
1 parent 41848ab commit d4a5890
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 1 deletion.
221 changes: 221 additions & 0 deletions ortools/routing/ils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,25 @@ bool HasPerformedNodes(const RoutingModel& model,
return false;
}

// Returns the number of used vehicles.
int CountUsedVehicles(const RoutingModel& model, const Assignment& assignment) {
int count = 0;
for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
count += model.Next(assignment, model.Start(vehicle)) != model.End(vehicle);
}
return count;
}

// Returns the average route size of non empty routes.
double ComputeAverageNonEmptyRouteSize(const RoutingModel& model,
const Assignment& assignment) {
const int num_used_vehicles = CountUsedVehicles(model, assignment);
if (num_used_vehicles == 0) return 0;

const double num_visits = model.Size() - model.vehicles();
return num_visits / num_used_vehicles;
}

// Returns a random performed visit for the given assignment. The procedure
// requires a distribution including all visits. Returns -1 if there are no
// performed visits.
Expand Down Expand Up @@ -594,6 +613,56 @@ int64_t RoutingSolution::GetRandomAdjacentVisit(
return next_node;
}

std::vector<int64_t> RoutingSolution::GetRandomSequenceOfVisits(
int64_t seed_visit, std::mt19937& rnd,
std::bernoulli_distribution& boolean_dist, int size) const {
DCHECK(BelongsToInitializedRoute(seed_visit));
DCHECK(!model_.IsStart(seed_visit));
DCHECK(!model_.IsEnd(seed_visit));
// The seed visit is actually performed.
DCHECK(CanBeRemoved(seed_visit));

// The seed visit is always included.
--size;

// Sequence's excluded boundaries.
int64_t left = GetInitializedPrevNodeIndex(seed_visit);
int64_t right = GetNextNodeIndex(seed_visit);

while (size-- > 0) {
if (model_.IsStart(left) && model_.IsEnd(right)) {
// We can no longer extend the sequence either way.
break;
}

// When left is at the start (resp. right is at the end), we can
// only extend right (resp. left), and if both ends are free to
// move we decide the direction at random.
if (model_.IsStart(left)) {
right = GetNextNodeIndex(right);
} else if (model_.IsEnd(right)) {
left = GetInitializedPrevNodeIndex(left);
} else {
const bool move_forward = boolean_dist(rnd);
if (move_forward) {
right = GetNextNodeIndex(right);
} else {
left = GetInitializedPrevNodeIndex(left);
}
}
}

// TODO(user): consider taking the container in input to avoid multiple
// memory allocations.
std::vector<int64_t> sequence;
int64_t curr = GetNextNodeIndex(left);
while (curr != right) {
sequence.push_back(curr);
curr = GetNextNodeIndex(curr);
}
return sequence;
}

CompositeRuinProcedure::CompositionStrategy::CompositionStrategy(
std::vector<RuinProcedure*> ruin_procedures)
: ruins_(std::move(ruin_procedures)) {}
Expand Down Expand Up @@ -825,6 +894,158 @@ int64_t RandomWalkRemovalRuinProcedure::GetNextNodeToRemove(
return same_route_closest_neighbor;
}

SISRRuinProcedure::SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd,
int num_neighbors)
: model_(*model),
rnd_(*rnd),
neighbors_manager_(model->GetOrCreateNodeNeighborsByCostClass(
{num_neighbors,
/*add_vehicle_starts_to_neighbors=*/false,
/*add_vehicle_ends_to_neighbors=*/false,
/*only_sort_neighbors_for_partial_neighborhoods=*/false})),
customer_dist_(0, model->Size() - model->vehicles()),
probability_dist_(0.0, 1.0),
ruined_routes_(model->vehicles()),
routing_solution_(*model) {}

std::function<int64_t(int64_t)> SISRRuinProcedure::Ruin(
const Assignment* assignment) {
const int64_t seed_node =
PickRandomPerformedVisit(model_, *assignment, rnd_, customer_dist_);
if (seed_node == -1) {
return [this, assignment](int64_t node) {
return assignment->Value(model_.NextVar(node));
};
}

routing_solution_.Reset(assignment);
ruined_routes_.SparseClearAll();

// TODO(user): add to proto.
const int max_cardinality_removed_sequences = 10;

// TODO(user): add to proto.
const int avg_num_removed_visits = 10;

const double max_sequence_size =
std::min<double>(max_cardinality_removed_sequences,
ComputeAverageNonEmptyRouteSize(model_, *assignment));

const double max_num_removed_sequences =
(4 * avg_num_removed_visits) / (1 + max_sequence_size) - 1;
DCHECK_GE(max_num_removed_sequences, 1);

const int num_sequences_to_remove =
std::floor(std::uniform_real_distribution<double>(
1.0, max_num_removed_sequences)(rnd_));

// We start by disrupting the route where the seed visit is served.
const int seed_route = RuinRoute(*assignment, seed_node, max_sequence_size);
DCHECK_NE(seed_route, -1);

const RoutingCostClassIndex cost_class_index =
model_.GetCostClassIndexOfVehicle(seed_route);

for (const int neighbor :
neighbors_manager_->GetOutgoingNeighborsOfNodeForCostClass(
cost_class_index.value(), seed_node)) {
if (ruined_routes_.NumberOfSetCallsWithDifferentArguments() ==
num_sequences_to_remove) {
break;
}

if (!routing_solution_.CanBeRemoved(neighbor)) {
continue;
}

RuinRoute(*assignment, neighbor, max_sequence_size);
}

return
[this](int64_t node) { return routing_solution_.GetNextNodeIndex(node); };
}

int SISRRuinProcedure::RuinRoute(const Assignment& assignment,
int64_t seed_visit,
double global_max_sequence_size) {
const int route = assignment.Value(model_.VehicleVar(seed_visit));
DCHECK_GE(route, 0);
if (ruined_routes_[route]) return -1;

routing_solution_.InitializeRouteInfoIfNeeded(route);
ruined_routes_.Set(route);

const double max_sequence_size = std::min<double>(
routing_solution_.GetRouteSize(route), global_max_sequence_size);

int sequence_size = std::floor(
std::uniform_real_distribution<double>(1.0, max_sequence_size)(rnd_));

if (sequence_size == 1 || sequence_size == max_sequence_size ||
boolean_dist_(rnd_)) {
RuinRouteWithSequenceProcedure(seed_visit, sequence_size);
} else {
RuinRouteWithSplitSequenceProcedure(route, seed_visit, sequence_size);
}

return route;
}

void SISRRuinProcedure::RuinRouteWithSequenceProcedure(int64_t seed_visit,
int sequence_size) {
const std::vector<int64_t> sequence =
routing_solution_.GetRandomSequenceOfVisits(seed_visit, rnd_,
boolean_dist_, sequence_size);

// Remove the selected visits.
for (const int64_t visit : sequence) {
routing_solution_.RemoveNode(visit);
}

// Remove any still performed pickup or delivery siblings.
for (const int64_t visit : sequence) {
routing_solution_.RemovePerformedPickupDeliverySibling(visit);
}
}

void SISRRuinProcedure::RuinRouteWithSplitSequenceProcedure(int64_t route,
int64_t seed_visit,
int sequence_size) {
// TODO(user): add to proto.
const double alpha = 0.01;

const int max_num_bypassed_visits =
routing_solution_.GetRouteSize(route) - sequence_size;
int num_bypassed_visits = 1;
while (num_bypassed_visits < max_num_bypassed_visits &&
probability_dist_(rnd_) >= alpha * probability_dist_(rnd_)) {
++num_bypassed_visits;
}

const std::vector<int64_t> sequence =
routing_solution_.GetRandomSequenceOfVisits(
seed_visit, rnd_, boolean_dist_, sequence_size + num_bypassed_visits);

const int start_bypassed_visits = rnd_() % (sequence_size + 1);
const int end_bypassed_visits = start_bypassed_visits + num_bypassed_visits;

// Remove the selected visits.
for (int i = 0; i < start_bypassed_visits; ++i) {
routing_solution_.RemoveNode(sequence[i]);
}
for (int i = end_bypassed_visits; i < sequence.size(); ++i) {
routing_solution_.RemoveNode(sequence[i]);
}

// Remove any still performed pickup or delivery siblings.
for (int i = 0; i < start_bypassed_visits; ++i) {
routing_solution_.RemovePerformedPickupDeliverySibling(sequence[i]);
}
for (int i = end_bypassed_visits; i < sequence.size(); ++i) {
routing_solution_.RemovePerformedPickupDeliverySibling(sequence[i]);
}
}

class RuinAndRecreateDecisionBuilder : public DecisionBuilder {
public:
RuinAndRecreateDecisionBuilder(
Expand Down
49 changes: 49 additions & 0 deletions ortools/routing/ils.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class RoutingSolution {
int64_t visit, std::mt19937& rnd,
std::bernoulli_distribution& boolean_dist) const;

// Returns a randomly selected sequence of contiguous visits that includes
// the seed visit.
// This must be called for a performed seed visit belonging to an
// initialized route.
std::vector<int64_t> GetRandomSequenceOfVisits(
int64_t seed_visit, std::mt19937& rnd,
std::bernoulli_distribution& boolean_dist, int size) const;

private:
const RoutingModel& model_;
std::vector<int64_t> nexts_;
Expand Down Expand Up @@ -185,6 +193,47 @@ class CompositeRuinProcedure : public RuinProcedure {
Assignment* next_assignment_;
};

// Performs a ruin based on the Slack Induction by String Removals (SISR)
// procedure described in "Slack Induction by String Removals for Vehicle
// Routing Problems" by Jan Christiaens and Greet Vanden Berghe, Transportation
// Science 2020.
// Link to paper:
// https://kuleuven.limo.libis.be/discovery/search?query=any,contains,LIRIAS1988666&tab=LIRIAS&search_scope=lirias_profile&vid=32KUL_KUL:Lirias&offset=0
// Note that, in this implementation, the notion of "string" is replaced by
// "sequence".
// In short, at every ruin application a number of routes are
// disrupted. This number of routes is selected according to a careful
// combination of user-defined parameters and solution and instance properties.
// Every selected route is then disrupted by removing a contiguous sequence of
// visits, possibly bypassing a contiguous subsequence.
class SISRRuinProcedure : public RuinProcedure {
public:
SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd, int num_neighbors);

std::function<int64_t(int64_t)> Ruin(const Assignment* assignment) override;

private:
int RuinRoute(const Assignment& assignment, int64_t seed_visit,
double global_max_sequence_size);

// Removes a randomly selected sequence that includes the given seed visit.
void RuinRouteWithSequenceProcedure(int64_t seed_visit, int sequence_size);

// Randomly removes a sequence including the seed visit but bypassing and
// preserving a random subsequence.
void RuinRouteWithSplitSequenceProcedure(int64_t route, int64_t seed_visit,
int sequence_size);

const RoutingModel& model_;
std::mt19937& rnd_;
const RoutingModel::NodeNeighborsByCostClass* const neighbors_manager_;
std::uniform_int_distribution<int64_t> customer_dist_;
std::bernoulli_distribution boolean_dist_;
std::uniform_real_distribution<double> probability_dist_;
SparseBitset<int64_t> ruined_routes_;
RoutingSolution routing_solution_;
};

// Returns a DecisionBuilder implementing a perturbation step of an Iterated
// Local Search approach.
DecisionBuilder* MakePerturbationDecisionBuilder(
Expand Down
2 changes: 1 addition & 1 deletion ortools/routing/parsers/capacity_planning.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ option java_package = "com.google.ortools.routing.parser";
option java_multiple_files = true;
option csharp_namespace = "Google.OrTools.Routing.Parser";

package operations_research;
package operations_research.routing;

// This is the proto for describing the multicommodity fixed-charged network
// design problem.
Expand Down

0 comments on commit d4a5890

Please sign in to comment.