Skip to content

Commit bd1157a

Browse files
committed
Merge branch 'feature/service-in-cost'
2 parents bac8464 + dea201e commit bd1157a

35 files changed

+868
-530
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
#### Features
88

9-
- Ability to set different tasks setup/service time per vehicle type (#336)
9+
- Ability to set different task times per vehicle type (#336)
10+
- Task times can be included in the cost used internally for optimization (#1130)
11+
- Support for cost per hour spent on tasks on a vehicle basis (#1130)
1012

1113
#### Internals
1214

docs/API.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ A `cost` object has the following properties:
132132
| ----------- | ----------- |
133133
| [`fixed`] | integer defining the cost of using this vehicle in the solution (defaults to `0`) |
134134
| [`per_hour`] | integer defining the cost for one hour of travel time with this vehicle (defaults to `3600`) |
135+
| [`per_task_hour`] | integer defining the cost for one hour of task time (setup + service) with this vehicle (defaults to `0`) |
135136
| [`per_km`] | integer defining the cost for one km of travel time with this vehicle (defaults to `0`) |
136137

137138
Using a non-default `per-hour` value means defining travel costs based

src/algorithms/heuristics/heuristics.cpp

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,31 +153,38 @@ template <class Route> struct UnassignedCosts {
153153
min_unassigned_to_route(input.jobs.size(),
154154
std::numeric_limits<Cost>::max()) {
155155
for (const auto job_rank : unassigned) {
156-
const auto unassigned_job_index = input.jobs[job_rank].index();
156+
const auto& unassigned_job = input.jobs[job_rank];
157+
const auto unassigned_job_index = unassigned_job.index();
158+
159+
// The purpose here is to generate insertion lower bounds so we
160+
// only account for service times (no setup) which are
161+
// independent of insertion rank.
162+
const auto added_service = unassigned_job.services[vehicle.type];
163+
const auto service_cost = vehicle.task_eval(added_service).cost;
157164

158165
if (vehicle.has_start()) {
159166
const auto start_to_job =
160167
vehicle.eval(vehicle.start.value().index(), unassigned_job_index)
161168
.cost;
162-
min_route_to_unassigned[job_rank] = start_to_job;
169+
min_route_to_unassigned[job_rank] = start_to_job + service_cost;
163170
}
164171

165172
if (vehicle.has_end()) {
166173
const auto job_to_end =
167174
vehicle.eval(unassigned_job_index, vehicle.end.value().index()).cost;
168-
min_unassigned_to_route[job_rank] = job_to_end;
175+
min_unassigned_to_route[job_rank] = job_to_end + service_cost;
169176
}
170177

171178
for (const auto j : route.route) {
172179
const auto job_index = input.jobs[j].index();
173180

174181
const auto job_to_unassigned =
175-
vehicle.eval(job_index, unassigned_job_index).cost;
182+
vehicle.eval(job_index, unassigned_job_index).cost + service_cost;
176183
min_route_to_unassigned[job_rank] =
177184
std::min(min_route_to_unassigned[job_rank], job_to_unassigned);
178185

179186
const auto unassigned_to_job =
180-
vehicle.eval(unassigned_job_index, job_index).cost;
187+
vehicle.eval(unassigned_job_index, job_index).cost + service_cost;
181188
min_unassigned_to_route[job_rank] =
182189
std::min(min_unassigned_to_route[job_rank], unassigned_to_job);
183190
}
@@ -215,15 +222,19 @@ template <class Route> struct UnassignedCosts {
215222
const std::set<Index>& unassigned,
216223
Index inserted_index) {
217224
for (const auto j : unassigned) {
218-
const auto unassigned_job_index = input.jobs[j].index();
225+
const auto& unassigned_job = input.jobs[j];
226+
const auto unassigned_job_index = unassigned_job.index();
227+
228+
const auto added_service = unassigned_job.services[vehicle.type];
229+
const auto service_cost = vehicle.task_eval(added_service).cost;
219230

220231
const auto to_unassigned =
221-
vehicle.eval(inserted_index, unassigned_job_index).cost;
232+
vehicle.eval(inserted_index, unassigned_job_index).cost + service_cost;
222233
min_route_to_unassigned[j] =
223234
std::min(min_route_to_unassigned[j], to_unassigned);
224235

225236
const auto from_unassigned =
226-
vehicle.eval(unassigned_job_index, inserted_index).cost;
237+
vehicle.eval(unassigned_job_index, inserted_index).cost + service_cost;
227238
min_unassigned_to_route[j] =
228239
std::min(min_unassigned_to_route[j], from_unassigned);
229240
}
@@ -279,7 +290,7 @@ inline Eval fill_route(const Input& input,
279290

280291
for (Index r = 0; r <= route.size(); ++r) {
281292
const auto current_eval =
282-
utils::addition_cost(input, job_rank, vehicle, route.route, r);
293+
utils::addition_eval(input, job_rank, vehicle, route.route, r);
283294

284295
const double current_cost =
285296
static_cast<double>(current_eval.cost) -
@@ -317,7 +328,7 @@ inline Eval fill_route(const Input& input,
317328
route.route.size() + 1);
318329

319330
for (unsigned d_rank = 0; d_rank <= route.route.size(); ++d_rank) {
320-
d_adds[d_rank] = utils::addition_cost(input,
331+
d_adds[d_rank] = utils::addition_eval(input,
321332
job_rank + 1,
322333
vehicle,
323334
route.route,
@@ -329,7 +340,7 @@ inline Eval fill_route(const Input& input,
329340
}
330341

331342
for (Index pickup_r = 0; pickup_r <= route.size(); ++pickup_r) {
332-
const auto p_add = utils::addition_cost(input,
343+
const auto p_add = utils::addition_eval(input,
333344
job_rank,
334345
vehicle,
335346
route.route,
@@ -370,7 +381,7 @@ inline Eval fill_route(const Input& input,
370381

371382
Eval current_eval;
372383
if (pickup_r == delivery_r) {
373-
current_eval = utils::addition_cost(input,
384+
current_eval = utils::addition_eval(input,
374385
job_rank,
375386
vehicle,
376387
route.route,

src/algorithms/local_search/insertion_search.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ compute_best_insertion_single(const Input& input,
4444
rank < sol_state.insertion_ranks_end[v][j];
4545
++rank) {
4646
const Eval current_eval =
47-
utils::addition_cost(input, j, v_target, route.route, rank);
47+
utils::addition_eval(input, j, v_target, route.route, rank);
4848
if (current_eval.cost < result.eval.cost &&
4949
v_target.ok_for_range_bounds(sol_state.route_evals[v] +
5050
current_eval) &&
@@ -113,7 +113,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
113113
bool found_valid = false;
114114
for (unsigned d_rank = begin_d_rank; d_rank < end_d_rank; ++d_rank) {
115115
d_adds[d_rank] =
116-
utils::addition_cost(input, j + 1, v_target, route.route, d_rank);
116+
utils::addition_eval(input, j + 1, v_target, route.route, d_rank);
117117
if (result.eval < d_adds[d_rank]) {
118118
valid_delivery_insertions[d_rank] = false;
119119
} else {
@@ -132,7 +132,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
132132
pickup_r < sol_state.insertion_ranks_end[v][j];
133133
++pickup_r) {
134134
const Eval p_add =
135-
utils::addition_cost(input, j, v_target, route.route, pickup_r);
135+
utils::addition_eval(input, j, v_target, route.route, pickup_r);
136136
if (result.eval < p_add) {
137137
// Even without delivery insertion more expensive than current best.
138138
continue;
@@ -173,7 +173,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input,
173173

174174
Eval pd_eval;
175175
if (pickup_r == delivery_r) {
176-
pd_eval = utils::addition_cost(input,
176+
pd_eval = utils::addition_eval(input,
177177
j,
178178
v_target,
179179
route.route,

src/algorithms/local_search/local_search.cpp

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,8 @@ LocalSearch<Route,
292292

293293
// Update best_route data required for consistency.
294294
modified_vehicles.insert(best_route);
295-
_sol_state.update_route_eval(_sol[best_route].route, best_route);
296-
_sol_state.set_insertion_ranks(_sol[best_route], best_route);
295+
_sol_state.update_route_eval(_sol[best_route]);
296+
_sol_state.set_insertion_ranks(_sol[best_route]);
297297

298298
const auto fixed_cost =
299299
_sol[best_route].empty() ? _input.vehicles[best_route].fixed_cost() : 0;
@@ -320,14 +320,14 @@ LocalSearch<Route,
320320
// Update stored data for consistency (except update_route_eval and
321321
// set_insertion_ranks done along the way).
322322
for (const auto v : modified_vehicles) {
323-
_sol_state.update_route_bbox(_sol[v].route, v);
324-
_sol_state.update_costs(_sol[v].route, v);
325-
_sol_state.update_skills(_sol[v].route, v);
326-
_sol_state.update_priorities(_sol[v].route, v);
327-
_sol_state.set_node_gains(_sol[v].route, v);
328-
_sol_state.set_edge_gains(_sol[v].route, v);
329-
_sol_state.set_pd_matching_ranks(_sol[v].route, v);
330-
_sol_state.set_pd_gains(_sol[v].route, v);
323+
_sol_state.update_route_bbox(_sol[v]);
324+
_sol_state.update_costs(_sol[v]);
325+
_sol_state.update_skills(_sol[v]);
326+
_sol_state.update_priorities(_sol[v]);
327+
_sol_state.set_node_gains(_sol[v]);
328+
_sol_state.set_edge_gains(_sol[v]);
329+
_sol_state.set_pd_matching_ranks(_sol[v]);
330+
_sol_state.set_pd_gains(_sol[v]);
331331
}
332332

333333
return modified_vehicles;
@@ -910,13 +910,16 @@ void LocalSearch<Route,
910910
continue;
911911
}
912912

913+
const unsigned jobs_moved_from_source =
914+
_sol[source].size() - s_rank - 1;
915+
913916
const auto& s_fwd_delivery = _sol[source].fwd_deliveries(s_rank);
914917
const auto& s_fwd_pickup = _sol[source].fwd_pickups(s_rank);
915918
const auto& s_bwd_delivery = _sol[source].bwd_deliveries(s_rank);
916919
const auto& s_bwd_pickup = _sol[source].bwd_pickups(s_rank);
917920

918921
Index end_t_rank = _sol[target].size();
919-
if (s_rank + 1 < _sol[source].size()) {
922+
if (jobs_moved_from_source > 0) {
920923
// There is a route end after s_rank in source route.
921924
const auto s_next_job_rank = _sol[source].route[s_rank + 1];
922925
end_t_rank =
@@ -930,6 +933,14 @@ void LocalSearch<Route,
930933
continue;
931934
}
932935

936+
assert(static_cast<int>(_sol[target].size()) - t_rank - 1 >= 0);
937+
if (const unsigned jobs_moved_from_target =
938+
_sol[target].size() - static_cast<unsigned>(t_rank) - 1;
939+
jobs_moved_from_source <= 2 && jobs_moved_from_target <= 2) {
940+
// One of Relocate, OrOpt, SwapStar, MixedExchange, or no-opt.
941+
continue;
942+
}
943+
933944
if (t_rank + 1 < static_cast<int>(_sol[target].size())) {
934945
// There is a route end after t_rank in target route.
935946
const auto t_next_job_rank = _sol[target].route[t_rank + 1];
@@ -1824,16 +1835,16 @@ void LocalSearch<Route,
18241835
#endif
18251836

18261837
for (auto v_rank : update_candidates) {
1827-
_sol_state.update_route_eval(_sol[v_rank].route, v_rank);
1828-
_sol_state.update_route_bbox(_sol[v_rank].route, v_rank);
1829-
_sol_state.update_costs(_sol[v_rank].route, v_rank);
1830-
_sol_state.update_skills(_sol[v_rank].route, v_rank);
1831-
_sol_state.update_priorities(_sol[v_rank].route, v_rank);
1832-
_sol_state.set_insertion_ranks(_sol[v_rank], v_rank);
1833-
_sol_state.set_node_gains(_sol[v_rank].route, v_rank);
1834-
_sol_state.set_edge_gains(_sol[v_rank].route, v_rank);
1835-
_sol_state.set_pd_matching_ranks(_sol[v_rank].route, v_rank);
1836-
_sol_state.set_pd_gains(_sol[v_rank].route, v_rank);
1838+
_sol_state.update_route_eval(_sol[v_rank]);
1839+
_sol_state.update_route_bbox(_sol[v_rank]);
1840+
_sol_state.update_costs(_sol[v_rank]);
1841+
_sol_state.update_skills(_sol[v_rank]);
1842+
_sol_state.update_priorities(_sol[v_rank]);
1843+
_sol_state.set_insertion_ranks(_sol[v_rank]);
1844+
_sol_state.set_node_gains(_sol[v_rank]);
1845+
_sol_state.set_edge_gains(_sol[v_rank]);
1846+
_sol_state.set_pd_matching_ranks(_sol[v_rank]);
1847+
_sol_state.set_pd_gains(_sol[v_rank]);
18371848

18381849
assert(_sol[v_rank].size() <= _input.vehicles[v_rank].max_tasks);
18391850
assert(_input.vehicles[v_rank].ok_for_range_bounds(
@@ -2009,22 +2020,22 @@ void LocalSearch<Route,
20092020
for (std::size_t v = 0; v < _sol.size(); ++v) {
20102021
// Update what is required for consistency in
20112022
// remove_from_route.
2012-
_sol_state.update_route_eval(_sol[v].route, v);
2013-
_sol_state.update_route_bbox(_sol[v].route, v);
2014-
_sol_state.set_node_gains(_sol[v].route, v);
2015-
_sol_state.set_pd_matching_ranks(_sol[v].route, v);
2016-
_sol_state.set_pd_gains(_sol[v].route, v);
2023+
_sol_state.update_costs(_sol[v]);
2024+
_sol_state.update_route_eval(_sol[v]);
2025+
_sol_state.update_route_bbox(_sol[v]);
2026+
_sol_state.set_node_gains(_sol[v]);
2027+
_sol_state.set_pd_matching_ranks(_sol[v]);
2028+
_sol_state.set_pd_gains(_sol[v]);
20172029
}
20182030
}
20192031

20202032
// Update stored data that has not been maintained while
20212033
// removing.
20222034
for (std::size_t v = 0; v < _sol.size(); ++v) {
2023-
_sol_state.update_costs(_sol[v].route, v);
2024-
_sol_state.update_skills(_sol[v].route, v);
2025-
_sol_state.update_priorities(_sol[v].route, v);
2026-
_sol_state.set_insertion_ranks(_sol[v], v);
2027-
_sol_state.set_edge_gains(_sol[v].route, v);
2035+
_sol_state.update_skills(_sol[v]);
2036+
_sol_state.update_priorities(_sol[v]);
2037+
_sol_state.set_insertion_ranks(_sol[v]);
2038+
_sol_state.set_edge_gains(_sol[v]);
20282039
}
20292040

20302041
// Refill jobs.

src/algorithms/local_search/route_split_utils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ compute_best_route_split_choice(const Input& input,
7373
}
7474

7575
const auto current_end_eval =
76-
-std::get<0>(utils::addition_cost_delta(input,
76+
-std::get<0>(utils::addition_eval_delta(input,
7777
sol_state,
7878
empty_routes[v_rank],
7979
0,
@@ -140,7 +140,7 @@ compute_best_route_split_choice(const Input& input,
140140
}
141141

142142
const auto current_begin_eval =
143-
-std::get<0>(utils::addition_cost_delta(input,
143+
-std::get<0>(utils::addition_eval_delta(input,
144144
sol_state,
145145
empty_routes[v_rank],
146146
0,

src/algorithms/local_search/swap_star_utils.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct SwapChoice {
3535

3636
SwapChoice() = default;
3737

38-
SwapChoice(Eval gain,
38+
SwapChoice(const Eval& gain,
3939
Index s_rank,
4040
Index t_rank,
4141
Index insertion_in_source,
@@ -219,7 +219,7 @@ SwapChoice compute_best_swap_star_choice(const Input& input,
219219

220220
for (unsigned s_rank = 0; s_rank < source.route.size(); ++s_rank) {
221221
const auto& target_insertions = top_insertions_in_target[s_rank];
222-
if (target_insertions[0].cost == NO_EVAL) {
222+
if (target_insertions[0].eval == NO_EVAL) {
223223
continue;
224224
}
225225

@@ -235,7 +235,7 @@ SwapChoice compute_best_swap_star_choice(const Input& input,
235235

236236
for (unsigned t_rank = 0; t_rank < target.route.size(); ++t_rank) {
237237
const auto& source_insertions = top_insertions_in_source[t_rank];
238-
if (source_insertions[0].cost == NO_EVAL) {
238+
if (source_insertions[0].eval == NO_EVAL) {
239239
continue;
240240
}
241241

@@ -252,14 +252,14 @@ SwapChoice compute_best_swap_star_choice(const Input& input,
252252
}
253253

254254
const auto target_in_place_delta =
255-
utils::in_place_delta_cost(input,
255+
utils::in_place_delta_eval(input,
256256
source.route[s_rank],
257257
t_v,
258258
target.route,
259259
t_rank);
260260

261261
const auto source_in_place_delta =
262-
utils::in_place_delta_cost(input,
262+
utils::in_place_delta_eval(input,
263263
target.route[t_rank],
264264
s_v,
265265
source.route,
@@ -295,8 +295,8 @@ SwapChoice compute_best_swap_star_choice(const Input& input,
295295

296296
for (const auto& ti : target_insertions) {
297297
if ((ti.rank != t_rank) && (ti.rank != t_rank + 1) &&
298-
(ti.cost != NO_EVAL)) {
299-
const Eval t_gain = target_delta - ti.cost;
298+
(ti.eval != NO_EVAL)) {
299+
const Eval t_gain = target_delta - ti.eval;
300300
current_gain = in_place_s_gain + t_gain;
301301
if (best_gain < current_gain &&
302302
t_v.ok_for_range_bounds(t_eval - t_gain)) {
@@ -320,8 +320,8 @@ SwapChoice compute_best_swap_star_choice(const Input& input,
320320
// target_insertions.
321321
for (const auto& si : source_insertions) {
322322
if ((si.rank != s_rank) && (si.rank != s_rank + 1) &&
323-
(si.cost != NO_EVAL)) {
324-
const Eval s_gain = source_delta - si.cost;
323+
(si.eval != NO_EVAL)) {
324+
const Eval s_gain = source_delta - si.eval;
325325

326326
if (!s_v.ok_for_range_bounds(s_eval - s_gain)) {
327327
// Don't bother further checking if max travel time
@@ -345,8 +345,8 @@ SwapChoice compute_best_swap_star_choice(const Input& input,
345345

346346
for (const auto& ti : target_insertions) {
347347
if ((ti.rank != t_rank) && (ti.rank != t_rank + 1) &&
348-
(ti.cost != NO_EVAL)) {
349-
const Eval t_gain = target_delta - ti.cost;
348+
(ti.eval != NO_EVAL)) {
349+
const Eval t_gain = target_delta - ti.eval;
350350
current_gain = s_gain + t_gain;
351351
if (best_gain < current_gain &&
352352
t_v.ok_for_range_bounds(t_eval - t_gain)) {

0 commit comments

Comments
 (0)