Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add max_distance constraint #991

Merged
merged 30 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
eb9f779
Remove Eval::operator>.
jcoupey Sep 18, 2023
e918e7a
Store pointer to distance data in CostWrapper.
jcoupey Sep 18, 2023
98cc05c
Populate distance data from Input::set_vehicles_costs.
jcoupey Sep 18, 2023
31abcb5
Expose internal Distance for edges from CostWrapper.
jcoupey Sep 18, 2023
aaa3a3b
Add distance member to Eval and adjust operators accordingly.
jcoupey Sep 18, 2023
bc4717a
Use default values from Eval ctor on fixed-cost only inits.
jcoupey Sep 18, 2023
9b2a53a
Make sure a distances matrix is always available.
jcoupey Sep 18, 2023
f2ea6e2
Store max_distance value at vehicle level.
jcoupey Sep 19, 2023
f56eaec
Parse max_distance in json input.
jcoupey Sep 19, 2023
56e18ea
Use the same pattern of passing an optional for max_tasks in vehicle …
jcoupey Sep 19, 2023
719c3a6
Factor various json value getters into a single template function.
jcoupey Sep 19, 2023
8671c33
Merge branch 'master' into feature/max-distance
jcoupey Sep 20, 2023
0ef5b7e
Use value_or on optionals.
jcoupey Sep 20, 2023
08dfbf6
Refactor Input::set_matrices to reduce nesting.
jcoupey Sep 20, 2023
941f779
Account for max distance when checking whether a vehicle has range li…
jcoupey Sep 21, 2023
ccecb98
Add Vehicle::ok_for_range_bounds for checks to both max_travel_time a…
jcoupey Sep 21, 2023
92d123f
Adjust max_travel_time logic to account for max distances.
jcoupey Sep 21, 2023
8c846c0
Handle max_distance constraint when using user-provided initial routes.
jcoupey Sep 21, 2023
2a92c9f
Document max_distance.
jcoupey Sep 21, 2023
1278db1
Rename operator<< for AmountExpression.
jcoupey Sep 22, 2023
bb7fb60
Improve capacity-based max_tasks bound, fixes #1000.
jcoupey Sep 22, 2023
f84f3bb
Add Vehicle::operator< using std::tie.
jcoupey Sep 22, 2023
0e32ae1
Simplify vehicle sorting in basic heuristic.
jcoupey Sep 22, 2023
fcc6af7
Simplify vehicle sorting in dynamic heuristic.
jcoupey Sep 25, 2023
1b23ffa
Avoid indirection problem in dynamic heuristic closest jobs count.
jcoupey Sep 25, 2023
5dbb9a1
Account for max travel time/distance in vehicle ordering.
jcoupey Sep 25, 2023
d9634ff
Vehicle ordering is not solely about capacity any more so rename SORT…
jcoupey Sep 25, 2023
3438849
Merge branch 'master' into feature/max-distance
jcoupey Sep 25, 2023
5d0ec99
Reuse existing ref in comparison.
jcoupey Sep 25, 2023
4fb7be3
Use template type for optional in get_value_for.
jcoupey Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_distance` at vehicle level (#354)
- Recommendation on how to cite in publications (#943)
- Store distance matrices (#956)
- Default radius of 35km for OSRM snapping (#922)
Expand Down
1 change: 1 addition & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ A `vehicle` object has the following properties:
| [`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 |
| [`max_distance`] | an integer defining the maximum distance for this vehicle |
| [`steps`] | an array of `vehicle_step` objects describing a custom route for this vehicle |

A `cost` object has the following properties:
Expand Down
109 changes: 50 additions & 59 deletions src/algorithms/heuristics/heuristics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,34 +44,24 @@ Eval basic(const Input& input,
std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks));

switch (sort) {
case SORT::CAPACITY:
// Sort vehicles by decreasing max number of tasks allowed, then
// capacity (not a total order), then working hours length.
case SORT::AVAILABILITY: {
// Sort vehicles by decreasing "availability".
std::ranges::stable_sort(vehicles_ranks,
[&](const auto lhs, const auto rhs) {
auto& v_lhs = input.vehicles[lhs];
auto& v_rhs = input.vehicles[rhs];
return v_lhs.max_tasks > v_rhs.max_tasks ||
(v_lhs.max_tasks == v_rhs.max_tasks &&
(v_rhs.capacity << v_lhs.capacity ||
(v_lhs.capacity == v_rhs.capacity &&
v_lhs.tw.length > v_rhs.tw.length)));
return input.vehicles[lhs] < input.vehicles[rhs];
});
break;
}
case SORT::COST:
// Sort vehicles by increasing fixed cost, then same as above.
std::ranges::stable_sort(vehicles_ranks,
[&](const auto lhs, const auto rhs) {
auto& v_lhs = input.vehicles[lhs];
auto& v_rhs = input.vehicles[rhs];
const auto& v_lhs = input.vehicles[lhs];
const auto& v_rhs = input.vehicles[rhs];
return v_lhs.costs < v_rhs.costs ||
(v_lhs.costs == v_rhs.costs &&
(v_lhs.max_tasks > v_rhs.max_tasks ||
(v_lhs.max_tasks == v_rhs.max_tasks &&
(v_rhs.capacity << v_lhs.capacity ||
(v_lhs.capacity == v_rhs.capacity &&
v_lhs.tw.length >
v_rhs.tw.length)))));
input.vehicles[lhs] <
input.vehicles[rhs]);
});
break;
}
Expand All @@ -80,7 +70,7 @@ Eval basic(const Input& input,

// regrets[v][j] holds the min cost for reaching job j in an empty
// route across all remaining vehicles **after** vehicle at rank v
// in vehicle_ranks.
// in vehicles_ranks.
std::vector<std::vector<Cost>> regrets(nb_vehicles,
std::vector<Cost>(input.jobs.size()));

Expand Down Expand Up @@ -133,8 +123,8 @@ Eval basic(const Input& input,
bool try_validity = false;

if (init == INIT::HIGHER_AMOUNT) {
try_validity |= (higher_amount << current_job.pickup ||
higher_amount << current_job.delivery);
try_validity |= (higher_amount < current_job.pickup ||
higher_amount < current_job.delivery);
}
if (init == INIT::EARLIEST_DEADLINE) {
Duration current_deadline =
Expand All @@ -154,7 +144,7 @@ Eval basic(const Input& input,
}

bool is_valid =
(vehicle.ok_for_travel_time(evals[job_rank][v_rank].duration)) &&
(vehicle.ok_for_range_bounds(evals[job_rank][v_rank])) &&
current_r.is_valid_addition_for_capacity(input,
current_job.pickup,
current_job.delivery,
Expand Down Expand Up @@ -183,10 +173,10 @@ Eval basic(const Input& input,
assert(false);
break;
case INIT::HIGHER_AMOUNT:
if (higher_amount << current_job.pickup) {
if (higher_amount < current_job.pickup) {
higher_amount = current_job.pickup;
}
if (higher_amount << current_job.delivery) {
if (higher_amount < current_job.delivery) {
higher_amount = current_job.delivery;
}
break;
Expand Down Expand Up @@ -258,8 +248,8 @@ Eval basic(const Input& input,
lambda * static_cast<double>(regrets[v][job_rank]);

if (current_cost < best_cost &&
(vehicle.ok_for_travel_time(current_route_eval.duration +
current_eval.duration)) &&
(vehicle.ok_for_range_bounds(current_route_eval +
current_eval)) &&
current_r.is_valid_addition_for_capacity(input,
current_job.pickup,
current_job.delivery,
Expand Down Expand Up @@ -355,8 +345,8 @@ Eval basic(const Input& input,

// Update best cost depending on validity.
bool valid =
(vehicle.ok_for_travel_time(current_route_eval.duration +
current_eval.duration)) &&
(vehicle.ok_for_range_bounds(current_route_eval +
current_eval)) &&
current_r
.is_valid_addition_for_capacity_inclusion(input,
modified_delivery,
Expand Down Expand Up @@ -425,7 +415,7 @@ Eval basic(const Input& input,

if (!current_r.empty()) {
sol_eval += current_route_eval;
sol_eval += Eval(vehicle.fixed_cost(), 0);
sol_eval += Eval(vehicle.fixed_cost());
}
}

Expand Down Expand Up @@ -487,7 +477,7 @@ Eval dynamic_vehicle_choice(const Input& input,

// Pick vehicle that has the biggest number of compatible jobs
// closest to him than to any other different vehicle.
std::vector<unsigned> closest_jobs_count(nb_vehicles, 0);
std::vector<unsigned> closest_jobs_count(input.vehicles.size(), 0);
for (const auto job_rank : unassigned) {
for (const auto v_rank : vehicles_ranks) {
if (evals[job_rank][v_rank].cost == jobs_min_costs[job_rank]) {
Expand All @@ -498,38 +488,35 @@ Eval dynamic_vehicle_choice(const Input& input,

Index v_rank;

if (sort == SORT::CAPACITY) {
if (sort == SORT::AVAILABILITY) {
const auto chosen_vehicle =
std::ranges::min_element(vehicles_ranks,
[&](const auto lhs, const auto rhs) {
auto& v_lhs = input.vehicles[lhs];
auto& v_rhs = input.vehicles[rhs];
return closest_jobs_count[lhs] >
closest_jobs_count[rhs] ||
(closest_jobs_count[lhs] ==
closest_jobs_count[rhs] &&
(v_rhs.capacity << v_lhs.capacity ||
(v_lhs.capacity == v_rhs.capacity &&
v_lhs.tw.length >
v_rhs.tw.length)));
input.vehicles[lhs] <
input.vehicles[rhs]);
});
v_rank = *chosen_vehicle;
vehicles_ranks.erase(chosen_vehicle);
} else {
assert(sort == SORT::COST);

const auto chosen_vehicle = std::ranges::
min_element(vehicles_ranks, [&](const auto lhs, const auto rhs) {
auto& v_lhs = input.vehicles[lhs];
auto& v_rhs = input.vehicles[rhs];
return closest_jobs_count[lhs] > closest_jobs_count[rhs] ||
(closest_jobs_count[lhs] == closest_jobs_count[rhs] &&
(v_lhs.costs < v_rhs.costs ||
(v_lhs.costs == v_rhs.costs &&
(v_rhs.capacity << v_lhs.capacity ||
(v_lhs.capacity == v_rhs.capacity &&
v_lhs.tw.length > v_rhs.tw.length)))));
});
const auto chosen_vehicle =
std::ranges::min_element(vehicles_ranks,
[&](const auto lhs, const auto rhs) {
const auto& v_lhs = input.vehicles[lhs];
const auto& v_rhs = input.vehicles[rhs];
return closest_jobs_count[lhs] >
closest_jobs_count[rhs] ||
(closest_jobs_count[lhs] ==
closest_jobs_count[rhs] &&
(v_lhs.costs < v_rhs.costs ||
(v_lhs.costs == v_rhs.costs &&
v_lhs < v_rhs)));
});
v_rank = *chosen_vehicle;
vehicles_ranks.erase(chosen_vehicle);
}
Expand Down Expand Up @@ -581,8 +568,8 @@ Eval dynamic_vehicle_choice(const Input& input,
bool try_validity = false;

if (init == INIT::HIGHER_AMOUNT) {
try_validity |= (higher_amount << current_job.pickup ||
higher_amount << current_job.delivery);
try_validity |= (higher_amount < current_job.pickup ||
higher_amount < current_job.delivery);
}
if (init == INIT::EARLIEST_DEADLINE) {
Duration current_deadline =
Expand All @@ -602,7 +589,7 @@ Eval dynamic_vehicle_choice(const Input& input,
}

bool is_valid =
(vehicle.ok_for_travel_time(evals[job_rank][v_rank].duration)) &&
(vehicle.ok_for_range_bounds(evals[job_rank][v_rank])) &&
current_r.is_valid_addition_for_capacity(input,
current_job.pickup,
current_job.delivery,
Expand Down Expand Up @@ -632,10 +619,10 @@ Eval dynamic_vehicle_choice(const Input& input,
assert(false);
break;
case INIT::HIGHER_AMOUNT:
if (higher_amount << current_job.pickup) {
if (higher_amount < current_job.pickup) {
higher_amount = current_job.pickup;
}
if (higher_amount << current_job.delivery) {
if (higher_amount < current_job.delivery) {
higher_amount = current_job.delivery;
}
break;
Expand Down Expand Up @@ -707,8 +694,8 @@ Eval dynamic_vehicle_choice(const Input& input,
lambda * static_cast<double>(regrets[job_rank]);

if (current_cost < best_cost &&
(vehicle.ok_for_travel_time(current_route_eval.duration +
current_eval.duration)) &&
(vehicle.ok_for_range_bounds(current_route_eval +
current_eval)) &&
current_r.is_valid_addition_for_capacity(input,
current_job.pickup,
current_job.delivery,
Expand Down Expand Up @@ -804,8 +791,8 @@ Eval dynamic_vehicle_choice(const Input& input,

// Update best cost depending on validity.
bool valid =
(vehicle.ok_for_travel_time(current_route_eval.duration +
current_eval.duration)) &&
(vehicle.ok_for_range_bounds(current_route_eval +
current_eval)) &&
current_r
.is_valid_addition_for_capacity_inclusion(input,
modified_delivery,
Expand Down Expand Up @@ -871,7 +858,7 @@ Eval dynamic_vehicle_choice(const Input& input,

if (!current_r.empty()) {
sol_eval += current_route_eval;
sol_eval += Eval(vehicle.fixed_cost(), 0);
sol_eval += Eval(vehicle.fixed_cost());
}
}

Expand Down Expand Up @@ -980,6 +967,10 @@ void initial_routes(const Input& input, std::vector<Route>& routes) {
throw InputException("Route over max_travel_time for vehicle " +
std::to_string(vehicle.id) + ".");
}
if (!vehicle.ok_for_distance(eval_sum.distance)) {
throw InputException("Route over max_distance for vehicle " +
std::to_string(vehicle.id) + ".");
}

if (vehicle.max_tasks < job_ranks.size()) {
throw InputException("Too many tasks for vehicle " +
Expand Down
11 changes: 5 additions & 6 deletions src/algorithms/local_search/insertion_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ compute_best_insertion_single(const Input& input,
Eval current_eval =
utils::addition_cost(input, j, v_target, route.route, rank);
if (current_eval.cost < result.eval.cost &&
v_target.ok_for_travel_time(sol_state.route_evals[v].duration +
current_eval.duration) &&
v_target.ok_for_range_bounds(sol_state.route_evals[v] +
current_eval) &&
route.is_valid_addition_for_capacity(input,
current_job.pickup,
current_job.delivery,
Expand Down Expand Up @@ -87,7 +87,6 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
RouteInsertion result(input.get_amount_size());
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 All @@ -106,7 +105,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
for (unsigned d_rank = begin_d_rank; d_rank < end_d_rank; ++d_rank) {
d_adds[d_rank] =
utils::addition_cost(input, j + 1, v_target, route.route, d_rank);
if (d_adds[d_rank] > result.eval) {
if (result.eval < d_adds[d_rank]) {
valid_delivery_insertions[d_rank] = false;
} else {
valid_delivery_insertions[d_rank] =
Expand All @@ -125,7 +124,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
++pickup_r) {
Eval p_add =
utils::addition_cost(input, j, v_target, route.route, pickup_r);
if (p_add > result.eval) {
if (result.eval < p_add) {
// Even without delivery insertion more expensive than current best.
continue;
}
Expand Down Expand Up @@ -176,7 +175,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
}

if (pd_eval < result.eval &&
v_target.ok_for_travel_time(target_travel_time + pd_eval.duration)) {
v_target.ok_for_range_bounds(sol_state.route_evals[v] + pd_eval)) {
modified_with_pd.push_back(j + 1);

// Update best cost depending on validity.
Expand Down
Loading