Skip to content

Commit

Permalink
Merge branch 'feature/max-travel-time'
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoupey committed Sep 20, 2022
2 parents 426263e + fca6dc1 commit b0fcb1b
Show file tree
Hide file tree
Showing 36 changed files with 584 additions and 400 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

- Support for `max_travel_time` at vehicle level (#273)
- Advertise `libvroom` in README and wiki (#42)

### Changed
Expand Down
1 change: 1 addition & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ A `vehicle` object has the following properties:
| [`breaks`] | an array of `break` objects |
| [`speed_factor`] | a double value in the range `(0, 5]` used to scale **all** vehicle travel times (defaults to 1.), the respected precision is limited to two digits after the decimal point |
| [`max_tasks`] | an integer defining the maximum number of tasks in a route for this vehicle |
| [`max_travel_time`] | an integer defining the maximum travel time for this vehicle |
| [`steps`] | an array of `vehicle_step` objects describing a custom route for this vehicle |

A `break` object has the following properties:
Expand Down
118 changes: 72 additions & 46 deletions src/algorithms/heuristics/heuristics.cpp

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions src/algorithms/local_search/insertion_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ compute_best_insertion_single(const Input& input,
const auto& v_target = input.vehicles[v];

if (input.vehicle_ok_with_job(v, j)) {

for (Index rank = sol_state.insertion_ranks_begin[v][j];
rank < sol_state.insertion_ranks_end[v][j];
++rank) {
Eval current_eval =
utils::addition_cost(input, j, v_target, route.route, rank);
if (current_eval.cost < result.eval.cost and
if (current_eval.cost < result.eval.cost &&
(sol_state.route_evals[v].duration + current_eval.duration <=
v_target.max_travel_time) &&
route.is_valid_addition_for_capacity(input,
current_job.pickup,
current_job.delivery,
rank) and
rank) &&
route.is_valid_addition_for_tw(input, j, rank)) {
result = {current_eval, rank, 0, 0};
}
Expand Down Expand Up @@ -81,6 +82,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
RouteInsertion result = empty_insert;
const auto& current_job = input.jobs[j];
const auto& v_target = input.vehicles[v];
const auto target_travel_time = sol_state.route_evals[v].duration;

if (!input.vehicle_ok_with_job(v, j)) {
return result;
Expand Down Expand Up @@ -119,7 +121,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
Eval p_add =
utils::addition_cost(input, j, v_target, route.route, pickup_r);
if (p_add > result.eval) {
// Even without delivery insertion more expensive then current best
// Even without delivery insertion more expensive than current best.
continue;
}

Expand Down Expand Up @@ -158,7 +160,8 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
pd_eval = p_add + d_adds[delivery_r];
}

if (pd_eval < result.eval) {
if (pd_eval < result.eval &&
target_travel_time + pd_eval.duration <= v_target.max_travel_time) {
modified_with_pd.push_back(j + 1);

// Update best cost depending on validity.
Expand All @@ -184,6 +187,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
}
}
}

assert(result.eval <= cost_threshold);
if (result.eval == cost_threshold) {
result.eval = NO_EVAL;
Expand Down
83 changes: 57 additions & 26 deletions src/algorithms/local_search/local_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ void LocalSearch<Route,
_sol_state.unassigned.erase(best_job_rank + 1);
}

// Update route/job insertions for best_route
// Update best_route data required for consistency.
_sol_state.update_route_eval(_sol[best_route].route, best_route);
_sol_state.set_insertion_ranks(_sol[best_route], best_route);

for (const auto j : _sol_state.unassigned) {
Expand All @@ -283,10 +284,6 @@ void LocalSearch<Route,
best_route,
_sol[best_route]);
}
#ifndef NDEBUG
// Update cost after addition.
_sol_state.update_route_cost(_sol[best_route].route, best_route);
#endif
}
} while (job_added);
}
Expand Down Expand Up @@ -1499,6 +1496,16 @@ void LocalSearch<Route,
continue;
}

const auto& v_s = _input.vehicles[s_t.first];
const auto s_travel_time = _sol_state.route_evals[s_t.first].duration;
const auto s_removal_duration_gain =
_sol_state.pd_gains[s_t.first][s_p_rank].duration;
if (s_travel_time > v_s.max_travel_time + s_removal_duration_gain) {
// Removing shipment from source route actually breaks
// max_travel_time constraint in source.
continue;
}

#ifdef LOG_LS_OPERATORS
++tried_moves[OperatorName::PDShift];
#endif
Expand Down Expand Up @@ -1643,28 +1650,37 @@ void LocalSearch<Route,

#ifndef NDEBUG
// Update route costs.
const auto previous_cost =
const auto previous_eval =
std::accumulate(update_candidates.begin(),
update_candidates.end(),
0,
Eval(),
[&](auto sum, auto c) {
return sum + _sol_state.route_costs[c];
return sum + _sol_state.route_evals[c];
});
#endif

for (auto v_rank : update_candidates) {
_sol_state.update_route_cost(_sol[v_rank].route, v_rank);
_sol_state.update_route_eval(_sol[v_rank].route, v_rank);

assert(_sol[v_rank].size() <= _input.vehicles[v_rank].max_tasks);
assert(_sol_state.route_evals[v_rank].duration <=
_input.vehicles[v_rank].max_travel_time);
}
const auto new_cost =

#ifndef NDEBUG
const auto new_eval =
std::accumulate(update_candidates.begin(),
update_candidates.end(),
0,
Eval(),
[&](auto sum, auto c) {
return sum + _sol_state.route_costs[c];
return sum + _sol_state.route_evals[c];
});
assert(new_cost + best_gain.cost == previous_cost);
assert(new_eval + best_gain == previous_eval);
#endif

for (auto v_rank : update_candidates) {
// Only this update (and update_route_eval done above) are
// actually required for consistency inside try_job_additions.
_sol_state.set_insertion_ranks(_sol[v_rank], v_rank);
}

Expand All @@ -1673,11 +1689,8 @@ void LocalSearch<Route,
0);

for (auto v_rank : update_candidates) {
// Running update_costs only after try_job_additions is fine.
_sol_state.update_costs(_sol[v_rank].route, v_rank);

_sol_state.update_skills(_sol[v_rank].route, v_rank);

_sol_state.set_node_gains(_sol[v_rank].route, v_rank);
_sol_state.set_edge_gains(_sol[v_rank].route, v_rank);
_sol_state.set_pd_matching_ranks(_sol[v_rank].route, v_rank);
Expand Down Expand Up @@ -1798,6 +1811,9 @@ void LocalSearch<Route,
for (unsigned i = 0; i < current_nb_removal; ++i) {
remove_from_routes();
for (std::size_t v = 0; v < _sol.size(); ++v) {
// Update what is required for consistency in
// remove_from_route.
_sol_state.update_route_eval(_sol[v].route, v);
_sol_state.set_node_gains(_sol[v].route, v);
_sol_state.set_pd_matching_ranks(_sol[v].route, v);
_sol_state.set_pd_gains(_sol[v].route, v);
Expand All @@ -1812,8 +1828,16 @@ void LocalSearch<Route,
// Refill jobs.
try_job_additions(_all_routes, 1.5);

// Reset what is needed in solution state.
_sol_state.setup(_sol);
// Update everything except what has already been updated in
// try_job_additions.
for (std::size_t v = 0; v < _sol.size(); ++v) {
_sol_state.update_costs(_sol[v].route, v);
_sol_state.update_skills(_sol[v].route, v);
_sol_state.set_node_gains(_sol[v].route, v);
_sol_state.set_edge_gains(_sol[v].route, v);
_sol_state.set_pd_matching_ranks(_sol[v].route, v);
_sol_state.set_pd_gains(_sol[v].route, v);
}
}

first_step = false;
Expand Down Expand Up @@ -2097,30 +2121,37 @@ void LocalSearch<Route,
Index best_rank = 0;
Eval best_gain = NO_GAIN;

const auto max_travel_time = _input.vehicles[v].max_travel_time;
const auto current_travel_time = _sol_state.route_evals[v].duration;

for (std::size_t r = 0; r < _sol[v].size(); ++r) {
const auto& current_job = _input.jobs[_sol[v].route[r]];
if (current_job.type == JOB_TYPE::DELIVERY) {
continue;
}

Eval current_gain;
bool valid_removal;
bool valid_removal = false;

if (current_job.type == JOB_TYPE::SINGLE) {
current_gain =
_sol_state.node_gains[v][r] - relocate_cost_lower_bound(v, r);
const auto& removal_gain = _sol_state.node_gains[v][r];
current_gain = removal_gain - relocate_cost_lower_bound(v, r);

if (current_gain > best_gain) {
// Only check validity if required.
valid_removal = _sol[v].is_valid_removal(_input, r, 1);
valid_removal =
(current_travel_time <= max_travel_time + removal_gain.duration) &&
_sol[v].is_valid_removal(_input, r, 1);
}
} else {
assert(current_job.type == JOB_TYPE::PICKUP);
auto delivery_r = _sol_state.matching_delivery_rank[v][r];
current_gain = _sol_state.pd_gains[v][r] -
relocate_cost_lower_bound(v, r, delivery_r);
const auto delivery_r = _sol_state.matching_delivery_rank[v][r];
const auto& removal_gain = _sol_state.pd_gains[v][r];
current_gain =
removal_gain - relocate_cost_lower_bound(v, r, delivery_r);

if (current_gain > best_gain) {
if (current_gain > best_gain &&
(current_travel_time <= max_travel_time + removal_gain.duration)) {
// Only check validity if required.
if (delivery_r == r + 1) {
valid_removal = _sol[v].is_valid_removal(_input, r, 2);
Expand Down
21 changes: 21 additions & 0 deletions src/algorithms/local_search/operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ Eval Operator::gain() {
return stored_gain;
}

bool Operator::is_valid_for_source_max_travel_time() const {
const auto& s_v = _input.vehicles[s_vehicle];
const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration;
return s_travel_time <= s_v.max_travel_time + s_gain.duration;
}

bool Operator::is_valid_for_target_max_travel_time() const {
const auto& t_v = _input.vehicles[t_vehicle];
const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration;
return t_travel_time <= t_v.max_travel_time + t_gain.duration;
}

bool Operator::is_valid_for_max_travel_time() const {
assert(s_vehicle == t_vehicle);
assert(gain_computed);

const auto& s_v = _input.vehicles[s_vehicle];
const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration;
return s_travel_time <= s_v.max_travel_time + stored_gain.duration;
}

std::vector<Index> Operator::required_unassigned() const {
return std::vector<Index>();
}
Expand Down
9 changes: 9 additions & 0 deletions src/algorithms/local_search/operator.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,19 @@ class Operator {
const Index t_rank;

bool gain_computed;
Eval s_gain;
Eval t_gain;
Eval stored_gain;

virtual void compute_gain() = 0;

bool is_valid_for_source_max_travel_time() const;

bool is_valid_for_target_max_travel_time() const;

// Used for internal operators only.
bool is_valid_for_max_travel_time() const;

public:
Operator(OperatorName name,
const Input& input,
Expand Down
Loading

0 comments on commit b0fcb1b

Please sign in to comment.