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

Better costs modelling at vehicle level #788

Merged
merged 46 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6c8fdbe
Run clang-format.
jcoupey Sep 20, 2022
b582901
Add VehicleCosts member to Vehicle.
jcoupey Sep 20, 2022
eea0242
Parse vehicle.costs.fixed in json input.
jcoupey Sep 21, 2022
4c61f5e
Simplify travel time computation in format_route.
jcoupey Sep 21, 2022
3c245fd
Use Eval for vroom::Route ctor.
jcoupey Sep 21, 2022
8e8e110
Use Eval in choose_ETA.
jcoupey Sep 21, 2022
ab23bf4
Add fixed cost in route_eval values.
jcoupey Sep 21, 2022
d7452dd
Report fixed cost in solution.
jcoupey Sep 21, 2022
69becdd
Take fixed costs into account in Relocate.
jcoupey Sep 21, 2022
b1b9c1f
Take fixed costs into account in OrOpt.
jcoupey Sep 21, 2022
ff63f7a
Take fixed costs into account in RouteExchange.
jcoupey Sep 22, 2022
6745bef
Take fixed costs into account in ReverseTwoOpt.
jcoupey Sep 22, 2022
ba59af3
Take fixed costs into account in PDShift.
jcoupey Sep 22, 2022
84b4b58
has_same_profile requires same fixed cost for vehicles.
jcoupey Sep 22, 2022
95abfdf
Update route_evals comment.
jcoupey Oct 19, 2022
cb01c4b
Add getters for fwd/bwd load peaks.
jcoupey Oct 19, 2022
05ee9e3
Allow computing eval on a route range only.
jcoupey Oct 19, 2022
1e5a943
First take at RouteSplit internal logic.
jcoupey Oct 19, 2022
86c32eb
First draft for cvrp::RouteSplit.
jcoupey Oct 19, 2022
f01891c
First draft for vrptw::RouteSplit.
jcoupey Oct 19, 2022
c01deed
Fist take at using RouteSplit in local search.
jcoupey Oct 19, 2022
5f6fe28
Make sure to invalidate RouteSplit moves if a candidate empty route h…
jcoupey Oct 20, 2022
fb90a94
Only try RouteSplit with heterogeneous fleet.
jcoupey Oct 20, 2022
48cc24f
Stop early in compute_best_route_split_choice whenever end route gain…
jcoupey Oct 20, 2022
ff9beaa
Mention RouteSplit in changelog.
jcoupey Oct 20, 2022
1dec941
Merge branch 'master' into feature/fixed-vehicle-cost
jcoupey Nov 14, 2022
91af2db
Store flag for homogeneous fixed costs.
jcoupey Nov 14, 2022
d62dafe
Add vehicle sorting option in heuristic parameters.
jcoupey Oct 21, 2022
51fcda8
Try another sorting option in heuristic for heteronegeous fixed costs.
jcoupey Nov 14, 2022
208da23
Merge branch 'master' into feature/fixed-vehicle-cost
jcoupey Nov 24, 2022
b1e7c86
Scale fixed cost internally to match with CostWrapper refactor.
jcoupey Nov 24, 2022
65fd5bd
Move scaling helpers to typedef file.
jcoupey Nov 24, 2022
0cc1af1
Adjust passing fixed cost as UserCost in Route ctor.
jcoupey Nov 24, 2022
5220148
Store a per_hour value in VehicleCosts.
jcoupey Nov 24, 2022
37ef4f7
Parse per_hour value in json vehicle.costs.
jcoupey Nov 24, 2022
70a5bab
Error when providing both a custom cost matrix and a non-default per_…
jcoupey Nov 24, 2022
d183b59
Adjust internal costs scaling to take per_hour valued into account.
jcoupey Nov 24, 2022
ab3cc8e
Scale back matrix values for TSP as solving stays in the UserCost world.
jcoupey Nov 25, 2022
d14412d
Consider profiles different with different per_hour costs.
jcoupey Nov 25, 2022
59d86e3
Separate notion of homogeneous profiles and costs.
jcoupey Nov 25, 2022
6e3d9e0
Also use per_hour when sorting vehicles based on costs.
jcoupey Nov 25, 2022
a4870f8
Account for fixed costs in try_job_additions.
jcoupey Nov 28, 2022
5b9f9a0
Do not break P&D precedence constraints in RouteSplit.
jcoupey Nov 28, 2022
d8e5f51
Add missing target fixed cost in PDShift evaluation.
jcoupey Nov 28, 2022
6f2f064
Document fixed and per_hour costs.
jcoupey Nov 28, 2022
9847c01
Remove unused include.
jcoupey Nov 30, 2022
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
### Added

- Support for `max_travel_time` at vehicle level (#273)
- Support for vehicle fixed costs (#528)
- Support for cost per hour for vehicles (#732)
- `RouteSplit` local search operator (#788)
- Advertise `libvroom` in README and wiki (#42)

### Changed
Expand Down
13 changes: 13 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ A `vehicle` object has the following properties:
| [`end`] | coordinates array |
| [`end_index`] | index of relevant row and column in custom matrices |
| [`capacity`] | an array of integers describing multidimensional quantities |
| [`costs`] | a `cost` object defining costs for this vehicle |
| [`skills`] | an array of integers defining skills |
| [`time_window`] | a `time_window` object describing working hours |
| [`breaks`] | an array of `break` objects |
Expand All @@ -118,6 +119,18 @@ A `vehicle` object has the following properties:
| [`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 `cost` object has the following properties:

| Key | Description |
| ----------- | ----------- |
| [`fixed`] | integer defining the cost of using this vehicle in the solution (defaults to `0`) |
| [`per_hour`] | integer defining the cost for one hour of travel time with this vehicle (defaults to `3600`) |

Using a non-default `per-hour` value means defining travel costs based
on travel times with a multiplicative factor. So in particular
providing a custom costs matrix for the vehicle is inconsistent and
will raise an error.

A `break` object has the following properties:

| Key | Description |
Expand Down
128 changes: 89 additions & 39 deletions src/algorithms/heuristics/heuristics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ std::vector<std::vector<Eval>> get_jobs_vehicles_evals(const Input& input) {
return evals;
}

template <class T> T basic(const Input& input, INIT init, double lambda) {
template <class T>
T basic(const Input& input, INIT init, double lambda, SORT sort) {
const auto nb_vehicles = input.vehicles.size();
T routes;
for (Index v = 0; v < nb_vehicles; ++v) {
Expand All @@ -77,19 +78,40 @@ template <class T> T basic(const Input& input, INIT init, double lambda) {
// within the heuristic.
std::vector<Index> vehicles_ranks(nb_vehicles);
std::iota(vehicles_ranks.begin(), vehicles_ranks.end(), 0);
// Sort vehicles by decreasing max number of tasks allowed, then
// capacity (not a total order), then working hours length.
std::stable_sort(vehicles_ranks.begin(),
vehicles_ranks.end(),
[&](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 or
(v_lhs.max_tasks == v_rhs.max_tasks and
(v_rhs.capacity << v_lhs.capacity or
(v_lhs.capacity == v_rhs.capacity and
v_lhs.tw.length > v_rhs.tw.length)));
});

switch (sort) {
case SORT::CAPACITY:
// Sort vehicles by decreasing max number of tasks allowed, then
// capacity (not a total order), then working hours length.
std::stable_sort(vehicles_ranks.begin(),
vehicles_ranks.end(),
[&](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 or
(v_lhs.max_tasks == v_rhs.max_tasks and
(v_rhs.capacity << v_lhs.capacity or
(v_lhs.capacity == v_rhs.capacity and
v_lhs.tw.length > v_rhs.tw.length)));
});
break;
case SORT::COST:
// Sort vehicles by increasing fixed cost, then same as above.
std::stable_sort(vehicles_ranks.begin(),
vehicles_ranks.end(),
[&](const auto lhs, const auto rhs) {
auto& v_lhs = input.vehicles[lhs];
auto& v_rhs = input.vehicles[rhs];
return v_lhs.costs < v_rhs.costs or
(v_lhs.costs == v_rhs.costs and
(v_lhs.max_tasks > v_rhs.max_tasks or
(v_lhs.max_tasks == v_rhs.max_tasks and
(v_rhs.capacity << v_lhs.capacity or
(v_lhs.capacity == v_rhs.capacity and
v_lhs.tw.length > v_rhs.tw.length)))));
});
break;
}

auto evals = get_jobs_vehicles_evals(input);

Expand Down Expand Up @@ -426,7 +448,10 @@ template <class T> T basic(const Input& input, INIT init, double lambda) {
}

template <class T>
T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) {
T dynamic_vehicle_choice(const Input& input,
INIT init,
double lambda,
SORT sort) {
const auto nb_vehicles = input.vehicles.size();
T routes;
for (Index v = 0; v < nb_vehicles; ++v) {
Expand Down Expand Up @@ -476,22 +501,47 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) {
}
}

const auto chosen_vehicle =
std::min_element(vehicles_ranks.begin(),
vehicles_ranks.end(),
[&](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] or
(closest_jobs_count[lhs] ==
closest_jobs_count[rhs] and
(v_rhs.capacity << v_lhs.capacity or
(v_lhs.capacity == v_rhs.capacity and
v_lhs.tw.length > v_rhs.tw.length)));
});
auto v_rank = *chosen_vehicle;
vehicles_ranks.erase(chosen_vehicle);
Index v_rank;

if (sort == SORT::CAPACITY) {
const auto chosen_vehicle =
std::min_element(vehicles_ranks.begin(),
vehicles_ranks.end(),
[&](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] or
(closest_jobs_count[lhs] ==
closest_jobs_count[rhs] and
(v_rhs.capacity << v_lhs.capacity or
(v_lhs.capacity == v_rhs.capacity and
v_lhs.tw.length > v_rhs.tw.length)));
});
v_rank = *chosen_vehicle;
vehicles_ranks.erase(chosen_vehicle);
} else {
assert(sort == SORT::COST);

const auto chosen_vehicle =
std::min_element(vehicles_ranks.begin(),
vehicles_ranks.end(),
[&](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] or
(closest_jobs_count[lhs] ==
closest_jobs_count[rhs] and
(v_lhs.costs < v_rhs.costs or
(v_lhs.costs == v_rhs.costs and
(v_rhs.capacity << v_lhs.capacity or
(v_lhs.capacity == v_rhs.capacity and
v_lhs.tw.length > v_rhs.tw.length)))));
});
v_rank = *chosen_vehicle;
vehicles_ranks.erase(chosen_vehicle);
}

// Once current vehicle is decided, regrets[j] holds the min cost
// of picking the job in an empty route for other remaining
Expand Down Expand Up @@ -915,19 +965,19 @@ template <class T> T initial_routes(const Input& input) {
using RawSolution = std::vector<RawRoute>;
using TWSolution = std::vector<TWRoute>;

template RawSolution basic(const Input& input, INIT init, double lambda);
template RawSolution
basic(const Input& input, INIT init, double lambda, SORT sort);

template RawSolution dynamic_vehicle_choice(const Input& input,
INIT init,
double lambda);
template RawSolution
dynamic_vehicle_choice(const Input& input, INIT init, double lambda, SORT sort);

template RawSolution initial_routes(const Input& input);

template TWSolution basic(const Input& input, INIT init, double lambda);
template TWSolution
basic(const Input& input, INIT init, double lambda, SORT sort);

template TWSolution dynamic_vehicle_choice(const Input& input,
INIT init,
double lambda);
template TWSolution
dynamic_vehicle_choice(const Input& input, INIT init, double lambda, SORT sort);

template TWSolution initial_routes(const Input& input);

Expand Down
8 changes: 6 additions & 2 deletions src/algorithms/heuristics/heuristics.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ namespace vroom {
namespace heuristics {

// Implementation of a variant of the Solomon I1 heuristic.
template <class T> T basic(const Input& input, INIT init, double lambda);
template <class T>
T basic(const Input& input, INIT init, double lambda, SORT sort);

// Adjusting the above for situation with heterogeneous fleet.
template <class T>
T dynamic_vehicle_choice(const Input& input, INIT init, double lambda);
T dynamic_vehicle_choice(const Input& input,
INIT init,
double lambda,
SORT sort);

// Populate routes with user-defined vehicle steps.
template <class T> T initial_routes(const Input& input);
Expand Down
Loading