Skip to content

Commit

Permalink
Merge branch 'fix/route-split'
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoupey committed Sep 25, 2023
2 parents b6942b5 + 44fa2db commit 9ffa2cf
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 60 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- Refactor heuristics to be able to operate on a subset of jobs and vehicles (#837)
- Account for vehicle/job compatibility in heuristic regrets values (#982)
- Slightly reduce computing times for SWAP* operator (#987)
- Refactor `RouteSplit` operator (#996)

### Fixed

Expand All @@ -40,6 +41,7 @@
- Meaningless `location_index` provided in output for break steps (#877)
- `max_travel_time` not accounted for with vehicle steps in solving mode (#954)
- `max_travel_time` not accounted for in `RouteSplit` (#941)
- Wrong capacity checks in `RouteSplit` (#981)
- Address sonarcloud "bugs" reports (#984)
- Address some sonarcloud "code smell" reports (#986)

Expand Down
12 changes: 7 additions & 5 deletions src/algorithms/local_search/local_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1647,13 +1647,10 @@ void LocalSearch<Route,
// RouteSplit stuff
std::vector<Index> empty_route_ranks;
empty_route_ranks.reserve(_input.vehicles.size());
std::vector<std::reference_wrapper<Route>> empty_route_refs;
empty_route_refs.reserve(_input.vehicles.size());

for (Index v = 0; v < _input.vehicles.size(); ++v) {
if (_sol[v].empty()) {
empty_route_ranks.push_back(v);
empty_route_refs.push_back(std::ref(_sol[v]));
}
}

Expand All @@ -1667,12 +1664,17 @@ void LocalSearch<Route,
#ifdef LOG_LS_OPERATORS
++tried_moves[OperatorName::RouteSplit];
#endif
// RouteSplit stores a const& to empty_route_ranks, which
// will be invalid in the unique_ptr created below after
// empty_route_ranks goes out of scope. This is fine because
// that ref is only stored to compute gain right below, not
// to apply the operator later on.
RouteSplit r(_input,
_sol_state,
_sol[source],
source,
std::move(empty_route_ranks),
std::move(empty_route_refs),
empty_route_ranks,
_sol,
best_gains[source][target]);

if (r.gain() > best_gains[source][target]) {
Expand Down
8 changes: 4 additions & 4 deletions src/algorithms/local_search/route_split_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ compute_best_route_split_choice(const Input& input,
auto second_best_end_eval = NO_EVAL;
Index second_v_end = 0; // dummy init

const auto& end_load = source.bwd_peak(r);
const auto end_max_load = source.sub_route_max_load_after(r);
const auto end_delivery = source.delivery_in_range(r, source.size());

for (Index v_rank = 0; v_rank < empty_route_ranks.size(); ++v_rank) {
const auto v = empty_route_ranks[v_rank];
const auto& end_v = input.vehicles[v];

if (r < sol_state.bwd_skill_rank[s_vehicle][v] ||
!(end_load <= end_v.capacity) ||
!(end_max_load <= end_v.capacity) ||
end_v.max_tasks < source.size() - r) {
continue;
}
Expand Down Expand Up @@ -131,15 +131,15 @@ compute_best_route_split_choice(const Input& input,
auto second_best_begin_eval = NO_EVAL;
Index second_v_begin = 0; // dummy init

const auto& begin_load = source.fwd_peak(r - 1);
const auto begin_max_load = source.sub_route_max_load_before(r);
const auto begin_delivery = source.delivery_in_range(0, r);

for (Index v_rank = 0; v_rank < empty_route_ranks.size(); ++v_rank) {
const auto v = empty_route_ranks[v_rank];
const auto& begin_v = input.vehicles[v];

if (sol_state.fwd_skill_rank[s_vehicle][v] < r ||
!(begin_load <= begin_v.capacity) || begin_v.max_tasks < r) {
!(begin_max_load <= begin_v.capacity) || begin_v.max_tasks < r) {
continue;
}

Expand Down
52 changes: 27 additions & 25 deletions src/problems/cvrp/operators/route_split.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ All rights reserved (see LICENSE).

namespace vroom::cvrp {

RouteSplit::RouteSplit(
const Input& input,
const utils::SolutionState& sol_state,
RawRoute& s_route,
Index s_vehicle,
std::vector<Index>&& empty_route_ranks,
std::vector<std::reference_wrapper<RawRoute>>&& empty_route_refs,
const Eval& best_known_gain)
std::vector<RawRoute> RouteSplit::dummy_sol;

RouteSplit::RouteSplit(const Input& input,
const utils::SolutionState& sol_state,
RawRoute& s_route,
Index s_vehicle,
const std::vector<Index>& empty_route_ranks,
std::vector<RawRoute>& sol,
const Eval& best_known_gain)
// Use dummy 0 values for unused ranks.
: Operator(OperatorName::RouteSplit,
input,
Expand All @@ -30,8 +31,8 @@ RouteSplit::RouteSplit(
s_vehicle,
0),
_best_known_gain(best_known_gain),
_empty_route_ranks(std::move(empty_route_ranks)),
_empty_route_refs(std::move(empty_route_refs)) {
_empty_route_ranks(empty_route_ranks),
_sol(sol) {
assert(s_route.size() >= 2);
assert(_empty_route_ranks.size() >= 2);
}
Expand All @@ -45,6 +46,11 @@ void RouteSplit::compute_gain() {
_best_known_gain);
if (choice.gain.cost > 0) {
stored_gain = choice.gain;

// Ranks in choice are relative to _empty_route_ranks so we go
// back to initial vehicle ranks in _sol.
_begin_route_rank = _empty_route_ranks[choice.v_begin];
_end_route_rank = _empty_route_ranks[choice.v_end];
}
gain_computed = true;
}
Expand All @@ -59,46 +65,42 @@ void RouteSplit::apply() {
assert(choice.gain != NO_GAIN);

// Empty route holding the end of the split.
auto& end_route = _empty_route_refs[choice.v_end].get();
auto& end_route = _sol[_end_route_rank];
assert(end_route.empty());
assert(end_route.vehicle_rank == _empty_route_ranks[choice.v_end]);

std::move(s_route.begin() + choice.split_rank,
s_route.end(),
std::back_inserter(end_route.route));
end_route.update_amounts(_input);
assert(end_route.max_load() ==
source.sub_route_max_load_after(choice.split_rank));

// Empty route holding the beginning of the split.
auto& begin_route = _empty_route_refs[choice.v_begin].get();
auto& begin_route = _sol[_begin_route_rank];
assert(begin_route.empty());
assert(begin_route.vehicle_rank == _empty_route_ranks[choice.v_begin]);

std::move(s_route.begin(),
s_route.begin() + choice.split_rank,
std::back_inserter(begin_route.route));
begin_route.update_amounts(_input);
assert(begin_route.max_load() ==
source.sub_route_max_load_before(choice.split_rank));

s_route.clear();

source.update_amounts(_input);
end_route.update_amounts(_input);
begin_route.update_amounts(_input);
}

std::vector<Index> RouteSplit::addition_candidates() const {
return {s_vehicle,
_empty_route_ranks[choice.v_begin],
_empty_route_ranks[choice.v_end]};
return {s_vehicle, _begin_route_rank, _end_route_rank};
}

std::vector<Index> RouteSplit::update_candidates() const {
return {s_vehicle,
_empty_route_ranks[choice.v_begin],
_empty_route_ranks[choice.v_end]};
return {s_vehicle, _begin_route_rank, _end_route_rank};
}

bool RouteSplit::invalidated_by(Index rank) const {
assert(choice.gain != NO_GAIN);
return rank == _empty_route_ranks[choice.v_begin] ||
rank == _empty_route_ranks[choice.v_end];
return rank == _begin_route_rank || rank == _end_route_rank;
}

} // namespace vroom::cvrp
12 changes: 8 additions & 4 deletions src/problems/cvrp/operators/route_split.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@ namespace vroom::cvrp {
class RouteSplit : public ls::Operator {
protected:
const Eval _best_known_gain;
const std::vector<Index> _empty_route_ranks;
const std::vector<std::reference_wrapper<RawRoute>> _empty_route_refs;
const std::vector<Index>& _empty_route_ranks;
std::vector<RawRoute>& _sol;
Index _begin_route_rank;
Index _end_route_rank;
ls::SplitChoice choice{ls::empty_route_split_choice};

static std::vector<RawRoute> dummy_sol;

void compute_gain() override;

public:
RouteSplit(const Input& input,
const utils::SolutionState& sol_state,
RawRoute& s_route,
Index s_vehicle,
std::vector<Index>&& empty_route_ranks,
std::vector<std::reference_wrapper<RawRoute>>&& empty_route_refs,
const std::vector<Index>& empty_route_ranks,
std::vector<RawRoute>& sol,
const Eval& best_known_gain);

bool is_valid() override;
Expand Down
36 changes: 21 additions & 15 deletions src/problems/vrptw/operators/route_split.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,22 @@ All rights reserved (see LICENSE).

namespace vroom::vrptw {

RouteSplit::RouteSplit(
const Input& input,
const utils::SolutionState& sol_state,
TWRoute& tw_s_route,
Index s_vehicle,
std::vector<Index>&& empty_route_ranks,
std::vector<std::reference_wrapper<TWRoute>>&& empty_route_refs,
const Eval& best_known_gain)
RouteSplit::RouteSplit(const Input& input,
const utils::SolutionState& sol_state,
TWRoute& tw_s_route,
Index s_vehicle,
const std::vector<Index>& empty_route_ranks,
std::vector<TWRoute>& sol,
const Eval& best_known_gain)
: cvrp::RouteSplit(input,
sol_state,
static_cast<RawRoute&>(tw_s_route),
s_vehicle,
std::move(empty_route_ranks),
std::vector<std::reference_wrapper<RawRoute>>(),
empty_route_ranks,
dummy_sol,
best_known_gain),
_tw_s_route(tw_s_route),
_empty_tw_route_refs(std::move(empty_route_refs)) {
_tw_sol(sol) {
}

void RouteSplit::compute_gain() {
Expand All @@ -41,6 +40,11 @@ void RouteSplit::compute_gain() {
_best_known_gain);
if (choice.gain.cost > 0) {
stored_gain = choice.gain;

// Ranks in choice are relative to _empty_route_ranks so we go
// back to initial vehicle ranks in _sol.
_begin_route_rank = _empty_route_ranks[choice.v_begin];
_end_route_rank = _empty_route_ranks[choice.v_end];
}
gain_computed = true;
}
Expand All @@ -49,9 +53,8 @@ void RouteSplit::apply() {
assert(choice.gain != NO_GAIN);

// Empty route holding the end of the split.
auto& end_route = _empty_tw_route_refs[choice.v_end].get();
auto& end_route = _tw_sol[_end_route_rank];
assert(end_route.empty());
assert(end_route.vehicle_rank == _empty_route_ranks[choice.v_end]);

const auto end_delivery =
_tw_s_route.delivery_in_range(choice.split_rank, _tw_s_route.size());
Expand All @@ -62,11 +65,12 @@ void RouteSplit::apply() {
s_route.end(),
0,
0);
assert(end_route.max_load() ==
_tw_s_route.sub_route_max_load_after(choice.split_rank));

// Empty route holding the beginning of the split.
auto& begin_route = _empty_tw_route_refs[choice.v_begin].get();
auto& begin_route = _tw_sol[_begin_route_rank];
assert(begin_route.empty());
assert(begin_route.vehicle_rank == _empty_route_ranks[choice.v_begin]);

const auto begin_delivery =
_tw_s_route.delivery_in_range(0, choice.split_rank);
Expand All @@ -77,6 +81,8 @@ void RouteSplit::apply() {
s_route.begin() + choice.split_rank,
0,
0);
assert(begin_route.max_load() ==
_tw_s_route.sub_route_max_load_before(choice.split_rank));

_tw_s_route.remove(_input, 0, s_route.size());
}
Expand Down
6 changes: 3 additions & 3 deletions src/problems/vrptw/operators/route_split.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace vroom::vrptw {
class RouteSplit : public cvrp::RouteSplit {
private:
TWRoute& _tw_s_route;
const std::vector<std::reference_wrapper<TWRoute>> _empty_tw_route_refs;
std::vector<TWRoute>& _tw_sol;

void compute_gain() override;

Expand All @@ -26,8 +26,8 @@ class RouteSplit : public cvrp::RouteSplit {
const utils::SolutionState& sol_state,
TWRoute& tw_s_route,
Index s_vehicle,
std::vector<Index>&& empty_route_ranks,
std::vector<std::reference_wrapper<TWRoute>>&& empty_route_refs,
const std::vector<Index>& empty_route_ranks,
std::vector<TWRoute>& sol,
const Eval& best_known_gain);

void apply() override;
Expand Down
20 changes: 16 additions & 4 deletions src/structures/vroom/raw_route.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ class RawRoute {

bool has_pickup_up_to_rank(const Index rank) const;

const Amount& max_load() const {
return _fwd_peaks.back();
}

const Amount& fwd_peak(Index rank) const {
return _fwd_peaks[rank];
}
Expand All @@ -93,6 +89,22 @@ class RawRoute {
return _bwd_peaks[rank];
}

const Amount& max_load() const {
return _fwd_peaks.back();
}

// Compute max load of sub-route spanning the [0; rank[ range.
Amount sub_route_max_load_before(Index rank) const {
assert(0 < rank && rank < size());
return _fwd_peaks[rank] - _bwd_deliveries[rank - 1];
}

// Compute max load of sub-route spanning the [rank; size[ range.
Amount sub_route_max_load_after(Index rank) const {
assert(0 < rank && rank < size());
return _bwd_peaks[rank] - _fwd_pickups[rank - 1];
}

// Check validity for addition of a given load in current route at
// rank.
bool is_valid_addition_for_capacity(const Input&,
Expand Down

0 comments on commit 9ffa2cf

Please sign in to comment.