Skip to content

Commit

Permalink
routing and constraint solver export from google3
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizux committed Sep 25, 2024
1 parent 8e24fad commit 858d4a7
Show file tree
Hide file tree
Showing 16 changed files with 694 additions and 465 deletions.
8 changes: 5 additions & 3 deletions ortools/constraint_solver/constraint_solveri.h
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,8 @@ class LocalSearchState {
DEFINE_STRONG_INT_TYPE(ConstraintId, int);
// Adds a variable domain to this state, returns a handler to the new domain.
VariableDomainId AddVariableDomain(int64_t relaxed_min, int64_t relaxed_max);
void RelaxVariableDomain(VariableDomainId domain_id);
// Relaxes the domain, returns false iff the domain was already relaxed.
bool RelaxVariableDomain(VariableDomainId domain_id);
bool TightenVariableDomainMin(VariableDomainId domain_id, int64_t value);
bool TightenVariableDomainMax(VariableDomainId domain_id, int64_t value);
int64_t VariableDomainMin(VariableDomainId domain_id) const;
Expand Down Expand Up @@ -2105,8 +2106,9 @@ class LocalSearchState::Variable {
}
void Relax() const {
if (state_ == nullptr) return;
state_->RelaxVariableDomain(domain_id_);
state_->PropagateRelax(domain_id_);
if (state_->RelaxVariableDomain(domain_id_)) {
state_->PropagateRelax(domain_id_);
}
}
bool Exists() const { return state_ != nullptr; }

Expand Down
4 changes: 3 additions & 1 deletion ortools/constraint_solver/local_search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3282,7 +3282,7 @@ LocalSearchState::Variable LocalSearchState::DummyVariable() {
return {nullptr, VariableDomainId(-1)};
}

void LocalSearchState::RelaxVariableDomain(VariableDomainId domain_id) {
bool LocalSearchState::RelaxVariableDomain(VariableDomainId domain_id) {
DCHECK(state_domains_are_all_nonempty_);
if (!state_has_relaxed_domains_) {
trailed_num_committed_empty_domains_ = num_committed_empty_domains_;
Expand All @@ -3297,7 +3297,9 @@ void LocalSearchState::RelaxVariableDomain(VariableDomainId domain_id) {
--num_committed_empty_domains_;
}
current_domains_[domain_id] = relaxed_domains_[domain_id];
return true;
}
return false;
}

int64_t LocalSearchState::VariableDomainMin(VariableDomainId domain_id) const {
Expand Down
7 changes: 4 additions & 3 deletions ortools/routing/decision_builders.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <cstdint>
#include <functional>
#include <limits>
#include <new>
#include <string>
#include <utility>
#include <vector>
Expand Down Expand Up @@ -356,10 +357,10 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
std::vector<int64_t>* break_start_end_values) {
cumul_values->clear();
break_start_end_values->clear();
const RouteDimensionTravelInfo& dimension_travel_info =
const RouteDimensionTravelInfo* const dimension_travel_info =
dimension_travel_info_per_route_.empty()
? RouteDimensionTravelInfo()
: dimension_travel_info_per_route_[vehicle];
? nullptr
: &dimension_travel_info_per_route_[vehicle];
const Resource* resource = nullptr;
if (rg_index_ >= 0 && model_.ResourceVar(vehicle, rg_index_)->Bound()) {
const int resource_index =
Expand Down
409 changes: 193 additions & 216 deletions ortools/routing/filters.cc

Large diffs are not rendered by default.

84 changes: 82 additions & 2 deletions ortools/routing/filters.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#ifndef OR_TOOLS_ROUTING_FILTERS_H_
#define OR_TOOLS_ROUTING_FILTERS_H_

#include <cstddef>
#include <cstdint>
#include <functional>
#include <initializer_list>
Expand All @@ -22,6 +23,7 @@
#include <utility>
#include <vector>

#include "absl/log/check.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/types.h"
Expand All @@ -36,6 +38,84 @@

namespace operations_research::routing {

// A vector that allows to revert back to a previously committed state,
// get the set of changed indices, and get current and committed values.
template <typename T>
class CommittableVector {
public:
// Makes a vector with initial elements all committed to value.
CommittableVector<T>(size_t num_elements, const T& value)
: elements_(num_elements, {value, value}), changed_(num_elements) {}

// Return the size of the vector.
size_t Size() const { return elements_.size(); }

// Returns a copy of the value stored at index in the current state.
// Does not return a reference, because the class needs to know when elements
// are modified.
T Get(size_t index) const {
DCHECK_LT(index, elements_.size());
return elements_[index].current;
}

// Set the value stored at index in the current state to given value.
void Set(size_t index, const T& value) {
DCHECK_GE(index, 0);
DCHECK_LT(index, elements_.size());
changed_.Set(index);
elements_[index].current = value;
}

// Changes the values of the vector to those in the last Commit().
void Revert() {
for (const size_t index : changed_.PositionsSetAtLeastOnce()) {
elements_[index].current = elements_[index].committed;
}
changed_.ClearAll();
}

// Makes the current state committed, clearing all changes.
void Commit() {
for (const size_t index : changed_.PositionsSetAtLeastOnce()) {
elements_[index].committed = elements_[index].current;
}
changed_.ClearAll();
}

// Sets all elements of this vector to given value, and commits to this state.
// Supposes that there are no changes since the last Commit() or Revert().
void SetAllAndCommit(const T& value) {
DCHECK_EQ(0, changed_.NumberOfSetCallsWithDifferentArguments());
elements_.assign(elements_.size(), {value, value});
}

// Returns a copy of the value stored at index in the last committed state.
T GetCommitted(size_t index) const {
DCHECK_LT(index, elements_.size());
return elements_[index].committed;
}

// Return true iff the value at index has been Set() since the last Commit()
// or Revert(), even if the current value is the same as the committed value.
bool HasChanged(size_t index) const { return changed_[index]; }

// Returns the set of indices that have been Set() since the last Commit() or
// Revert().
const std::vector<size_t>& ChangedIndices() const {
return changed_.PositionsSetAtLeastOnce();
}

private:
struct VersionedElement {
T current;
T committed;
};
// Holds current and committed versions of values of this vector.
std::vector<VersionedElement> elements_;
// Holds indices that were Set() since the last Commit() or Revert().
SparseBitset<size_t> changed_;
};

/// Returns a filter tracking route constraints.
IntVarLocalSearchFilter* MakeRouteConstraintFilter(
const RoutingModel& routing_model);
Expand Down Expand Up @@ -76,15 +156,15 @@ IntVarLocalSearchFilter* MakeVehicleVarFilter(
IntVarLocalSearchFilter* MakePathCumulFilter(const RoutingDimension& dimension,
bool propagate_own_objective_value,
bool filter_objective_cost,
bool can_use_lp);
bool may_use_optimizers);

/// Returns a filter handling dimension cumul bounds.
IntVarLocalSearchFilter* MakeCumulBoundsPropagatorFilter(
const RoutingDimension& dimension);

/// Returns a filter checking global linear constraints and costs.
IntVarLocalSearchFilter* MakeGlobalLPCumulFilter(
GlobalDimensionCumulOptimizer* optimizer,
GlobalDimensionCumulOptimizer* lp_optimizer,
GlobalDimensionCumulOptimizer* mp_optimizer, bool filter_objective_cost);

/// Returns a filter checking the feasibility and cost of the resource
Expand Down
31 changes: 16 additions & 15 deletions ortools/routing/ils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ std::unique_ptr<RuinProcedure> MakeRuinProcedure(
return std::make_unique<RandomWalkRemovalRuinProcedure>(
model, rnd, ruin.random_walk().num_removed_visits(),
num_neighbors_for_route_selection);
case RuinStrategy::kSisr:
return std::make_unique<SISRRuinProcedure>(
model, rnd, ruin.sisr().max_removed_sequence_size(),
ruin.sisr().avg_num_removed_visits(), ruin.sisr().bypass_factor(),
num_neighbors_for_route_selection);
default:
LOG(DFATAL) << "Unsupported ruin procedure.";
return nullptr;
Expand Down Expand Up @@ -895,9 +900,14 @@ int64_t RandomWalkRemovalRuinProcedure::GetNextNodeToRemove(
}

SISRRuinProcedure::SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd,
int num_neighbors)
int max_removed_sequence_size,
int avg_num_removed_visits,
double bypass_factor, int num_neighbors)
: model_(*model),
rnd_(*rnd),
max_removed_sequence_size_(max_removed_sequence_size),
avg_num_removed_visits_(avg_num_removed_visits),
bypass_factor_(bypass_factor),
neighbors_manager_(model->GetOrCreateNodeNeighborsByCostClass(
{num_neighbors,
/*add_vehicle_starts_to_neighbors=*/false,
Expand All @@ -921,23 +931,17 @@ std::function<int64_t(int64_t)> SISRRuinProcedure::Ruin(
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,
std::min<double>(max_removed_sequence_size_,
ComputeAverageNonEmptyRouteSize(model_, *assignment));

const double max_num_removed_sequences =
(4 * avg_num_removed_visits) / (1 + max_sequence_size) - 1;
(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_));
1.0, max_num_removed_sequences + 1)(rnd_));

// We start by disrupting the route where the seed visit is served.
const int seed_route = RuinRoute(*assignment, seed_node, max_sequence_size);
Expand Down Expand Up @@ -979,7 +983,7 @@ int SISRRuinProcedure::RuinRoute(const Assignment& assignment,
routing_solution_.GetRouteSize(route), global_max_sequence_size);

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

if (sequence_size == 1 || sequence_size == max_sequence_size ||
boolean_dist_(rnd_)) {
Expand Down Expand Up @@ -1011,14 +1015,11 @@ void SISRRuinProcedure::RuinRouteWithSequenceProcedure(int64_t seed_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_)) {
probability_dist_(rnd_) >= bypass_factor_ * probability_dist_(rnd_)) {
++num_bypassed_visits;
}

Expand Down
8 changes: 7 additions & 1 deletion ortools/routing/ils.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,12 @@ class CompositeRuinProcedure : public RuinProcedure {
// 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.
// See also SISRRuinStrategy in ils.proto.
class SISRRuinProcedure : public RuinProcedure {
public:
SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd, int num_neighbors);
SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd,
int max_removed_sequence_size, int avg_num_removed_visits,
double bypass_factor, int num_neighbors);

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

Expand All @@ -226,6 +229,9 @@ class SISRRuinProcedure : public RuinProcedure {

const RoutingModel& model_;
std::mt19937& rnd_;
int max_removed_sequence_size_;
int avg_num_removed_visits_;
double bypass_factor_;
const RoutingModel::NodeNeighborsByCostClass* const neighbors_manager_;
std::uniform_int_distribution<int64_t> customer_dist_;
std::bernoulli_distribution boolean_dist_;
Expand Down
88 changes: 88 additions & 0 deletions ortools/routing/ils.proto
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,99 @@ message RandomWalkRuinStrategy {
optional uint32 num_removed_visits = 7;
}

// Ruin strategy based on the "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/fulldisplay?docid=lirias1988666&context=SearchWebhook&vid=32KUL_KUL:Lirias&lang=en&search_scope=lirias_profile&adaptor=SearchWebhook&tab=LIRIAS&query=any,contains,LIRIAS1988666&offset=0
//
// Note that, in this implementation, the notion of "string" is replaced by
// "sequence".
//
// The main idea of this ruin is to remove a number of geographically close
// sequences of nodes. In particular, at every ruin application, a maximum
// number max_ruined_routes of routes are disrupted. The value for
// max_ruined_routes is defined as
// (4 * avg_num_removed_visits) / (1 + max_sequence_size) + 1
// with
// - avg_num_removed_visits: user-defined parameter ruling the average number of
// visits that are removed in face of several ruin applications (see also the
// proto message below)
// - max_sequence_size is defined as
// min{max_removed_sequence_size, average_route_size}
// with
// - max_removed_sequence_size: user-defined parameter that specifies
// the maximum number of visits removed from a single route (see also the
// proto message below)
// - average_route_size: the average size of a non-empty route in the current
// solution
//
// The actual number of ruined routes is then obtained as
// floor(U(1, max_ruined_routes + 1))
// where U is a continuous uniform distribution of real values in the given
// interval.
//
// The routes affected by the ruin procedure are selected as follows.
// First, a non start/end seed node is randomly selected. The route serving this
// node is the first ruined route. Then, until the required number of routes has
// been ruined, neighbor nodes of the initial seed node are scanned and the
// associated not yet ruined routes are disrupted. Nodes defining the selected
// routes are designated as seed nodes for the "sequence" and "split sequence"
// removal procedures described below.
//
// For every selected route, a maximum number route_max_sequence_size of nodes
// are removed. In particular, route_max_sequence_size is defined as
// min{route_size, max_sequence_size}
// with route_size being the size of the current route.
//
// Then, the actual number of removed nodes num_removed_nodes is defined as
// floor(U(1, route_max_sequence_size + 1))
// where U is a continuous uniform distribution of real values in the given
// interval.
//
// As mentioned above, the selected num_removed_nodes number of nodes is removed
// either via the "sequence" removal or "split sequence" removal procedures. The
// two removal procedures are executed with equal probabilities.
//
// The "sequence" removal procedure removes a randomly selected sequence of size
// num_removed_nodes that includes the seed node.
//
// The "split sequence" removal procedure also removes a randomly selected
// sequence of size num_removed_nodes that includes the seed node, but it can
// possibly preserve a subsequence of contiguous nodes.
// In particular, the procedure first selects a sequence of size
// num_removed_nodes + num_bypassed, then num_bypassed contiguous nodes in the
// selected sequence are preserved while the others removed.
//
// The definition of num_bypassed is as follows. First num_bypassed = 1. The
// current value of num_bypassed is maintaned if
// U(0, 1) < bypass_factor * U(0, 1)
// or the maximum value for num_bypassed, equal to
// route_size - num_removed_nodes
// is reached. The value is incremented of a unit otherwise,
// and the process is repeated. The value assigned to bypass_factor affects the
// number of preserved visits (see also the proto message below).
message SISRRuinStrategy {
// Maximum number of removed visits per sequence. The parameter name in the
// paper is L^{max} and the suggested value is 10.
optional uint32 max_removed_sequence_size = 1;

// Number of visits that are removed on average. The parameter name in the
// paper is \bar{c} and the suggested value is 10.
optional uint32 avg_num_removed_visits = 2;

// Value in [0, 1] ruling the number of preserved customers in the split
// sequence removal. The parameter name in the paper is \alpha and the
// suggested value is 0.01.
optional double bypass_factor = 3;
}

// Ruin strategies, used in perturbation based on ruin and recreate approaches.
message RuinStrategy {
oneof strategy {
SpatiallyCloseRoutesRuinStrategy spatially_close_routes = 1;
RandomWalkRuinStrategy random_walk = 2;
SISRRuinStrategy sisr = 3;
}
}

Expand Down
Loading

0 comments on commit 858d4a7

Please sign in to comment.