Skip to content

Commit

Permalink
Merge branch 'core/recreate-complexity'
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoupey committed Sep 17, 2024
2 parents f6ae995 + 38545d9 commit 81b2a95
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 171 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

- Refactor `Matrix` template class (#1089)
- Refactor to use `std::format` whenever possible (#1081)
- Reduce complexity for recreation process (#1155)

#### CI

Expand Down
239 changes: 77 additions & 162 deletions src/algorithms/local_search/local_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,180 +161,98 @@ void LocalSearch<Route,
SwapStar,
RouteSplit,
PriorityReplace,
TSPFix>::try_job_additions(const std::vector<Index>& routes,
double regret_coeff
TSPFix>::recreate(const std::vector<Index>& routes
#ifdef LOG_LS
,
bool log_addition_step
,
bool log_addition_step
#endif
) {
bool job_added;
std::vector<Index> ordered_jobs(_sol_state.unassigned.begin(),
_sol_state.unassigned.end());
std::ranges::sort(ordered_jobs, [this](const Index lhs, const Index rhs) {
return _input.jobs[lhs] < _input.jobs[rhs];
});

std::vector<std::vector<RouteInsertion>> route_job_insertions;
route_job_insertions.reserve(routes.size());

for (std::size_t i = 0; i < routes.size(); ++i) {
route_job_insertions.emplace_back(_input.jobs.size(),
RouteInsertion(_input.get_amount_size()));

const auto v = routes[i];
const auto fixed_cost =
_sol[v].empty() ? _input.vehicles[v].fixed_cost() : 0;

for (const auto j : _sol_state.unassigned) {
if (const auto& current_job = _input.jobs[j];
current_job.type == JOB_TYPE::DELIVERY) {
continue;
}
route_job_insertions[i][j] =
compute_best_insertion(_input, _sol_state, j, v, _sol[v]);
std::unordered_set<Index> modified_vehicles;

if (route_job_insertions[i][j].eval != NO_EVAL) {
route_job_insertions[i][j].eval.cost += fixed_cost;
}
for (const auto j : ordered_jobs) {
const auto& current_job = _input.jobs[j];
if (current_job.type == JOB_TYPE::DELIVERY) {
continue;
}
}

std::unordered_set<Index> modified_vehicles;

do {
Priority best_priority = 0;
RouteInsertion best_insertion(_input.get_amount_size());
double best_cost = std::numeric_limits<double>::max();
Index best_job_rank = 0;
Index best_route = 0;
std::size_t best_route_idx = 0;
Index best_v = 0;

for (const auto j : _sol_state.unassigned) {
const auto& current_job = _input.jobs[j];
if (current_job.type == JOB_TYPE::DELIVERY) {
for (const auto v : routes) {
if (const unsigned added_tasks =
(current_job.type == JOB_TYPE::PICKUP) ? 2 : 1;
_sol[v].size() + added_tasks > _input.vehicles[v].max_tasks) {
continue;
}

const auto job_priority = current_job.priority;

if (job_priority < best_priority) {
// Insert higher priority jobs first.
continue;
}
auto current_best_insertion =
compute_best_insertion(_input, _sol_state, j, v, _sol[v]);

auto smallest = _input.get_cost_upper_bound();
auto second_smallest = _input.get_cost_upper_bound();
std::size_t smallest_idx = std::numeric_limits<std::size_t>::max();

for (std::size_t i = 0; i < routes.size(); ++i) {
if (route_job_insertions[i][j].eval.cost < smallest) {
smallest_idx = i;
second_smallest = smallest;
smallest = route_job_insertions[i][j].eval.cost;
} else if (route_job_insertions[i][j].eval.cost < second_smallest) {
second_smallest = route_job_insertions[i][j].eval.cost;
}
if (current_best_insertion.eval != NO_EVAL && _sol[v].empty()) {
current_best_insertion.eval.cost += _input.vehicles[v].fixed_cost();
}

// Find best route for current job based on cost of addition and
// regret cost of not adding.
for (std::size_t i = 0; i < routes.size(); ++i) {
if (route_job_insertions[i][j].eval == NO_EVAL) {
continue;
}

const auto& current_r = _sol[routes[i]];
const auto& vehicle = _input.vehicles[routes[i]];

if (bool is_pickup = (_input.jobs[j].type == JOB_TYPE::PICKUP);
current_r.size() + (is_pickup ? 2 : 1) > vehicle.max_tasks) {
continue;
}

const auto regret_cost =
(i == smallest_idx) ? second_smallest : smallest;

const double current_cost =
static_cast<double>(route_job_insertions[i][j].eval.cost) -
regret_coeff * static_cast<double>(regret_cost);

if ((job_priority > best_priority) ||
(job_priority == best_priority && current_cost < best_cost)) {
best_priority = job_priority;
best_job_rank = j;
best_route = routes[i];
best_insertion = route_job_insertions[i][j];
best_cost = current_cost;
best_route_idx = i;
}
if (current_best_insertion.eval < best_insertion.eval) {
best_insertion = current_best_insertion;
best_v = v;
}
}

job_added = (best_cost < std::numeric_limits<double>::max());
if (best_insertion.eval == NO_EVAL) {
// Current job cannot be inserted.
continue;
}

if (job_added) {
_sol_state.unassigned.erase(best_job_rank);
// Found a valid insertion spot for current job.
_sol_state.unassigned.erase(j);

if (const auto& best_job = _input.jobs[best_job_rank];
best_job.type == JOB_TYPE::SINGLE) {
_sol[best_route].add(_input, best_job_rank, best_insertion.single_rank);
} else {
assert(best_job.type == JOB_TYPE::PICKUP);

std::vector<Index> modified_with_pd;
modified_with_pd.reserve(best_insertion.delivery_rank -
best_insertion.pickup_rank + 2);
modified_with_pd.push_back(best_job_rank);

std::copy(_sol[best_route].route.begin() + best_insertion.pickup_rank,
_sol[best_route].route.begin() + best_insertion.delivery_rank,
std::back_inserter(modified_with_pd));
modified_with_pd.push_back(best_job_rank + 1);

_sol[best_route].replace(_input,
best_insertion.delivery,
modified_with_pd.begin(),
modified_with_pd.end(),
best_insertion.pickup_rank,
best_insertion.delivery_rank);

assert(_sol_state.unassigned.find(best_job_rank + 1) !=
_sol_state.unassigned.end());
_sol_state.unassigned.erase(best_job_rank + 1);
}
if (current_job.type == JOB_TYPE::SINGLE) {
_sol[best_v].add(_input, j, best_insertion.single_rank);
} else {
assert(current_job.type == JOB_TYPE::PICKUP);

// Update best_route data required for consistency.
modified_vehicles.insert(best_route);
_sol_state.update_route_eval(_sol[best_route].route, best_route);
_sol_state.set_insertion_ranks(_sol[best_route], best_route);
std::vector<Index> modified_with_pd;
modified_with_pd.reserve(best_insertion.delivery_rank -
best_insertion.pickup_rank + 2);
modified_with_pd.push_back(j);

std::copy(_sol[best_v].route.begin() + best_insertion.pickup_rank,
_sol[best_v].route.begin() + best_insertion.delivery_rank,
std::back_inserter(modified_with_pd));
modified_with_pd.push_back(j + 1);

_sol[best_v].replace(_input,
best_insertion.delivery,
modified_with_pd.begin(),
modified_with_pd.end(),
best_insertion.pickup_rank,
best_insertion.delivery_rank);

assert(_sol_state.unassigned.find(j + 1) != _sol_state.unassigned.end());
_sol_state.unassigned.erase(j + 1);
}

const auto fixed_cost =
_sol[best_route].empty() ? _input.vehicles[best_route].fixed_cost() : 0;

for (const auto j : _sol_state.unassigned) {
if (const auto& current_job = _input.jobs[j];
current_job.type == JOB_TYPE::DELIVERY) {
continue;
}
route_job_insertions[best_route_idx][j] =
compute_best_insertion(_input,
_sol_state,
j,
best_route,
_sol[best_route]);

if (route_job_insertions[best_route_idx][j].eval != NO_EVAL) {
route_job_insertions[best_route_idx][j].eval.cost += fixed_cost;
}
}
// Update best route data, required for consistency.
modified_vehicles.insert(best_v);
_sol_state.update_route_eval(_sol[best_v].route, best_v);
_sol_state.set_insertion_ranks(_sol[best_v], best_v);

#ifdef LOG_LS
if (log_addition_step) {
steps.push_back({utils::now(),
log::EVENT::JOB_ADDITION,
OperatorName::MAX,
utils::SolutionIndicators<Route>(_input, _sol),
std::nullopt});
}
#endif
if (log_addition_step) {
steps.push_back({utils::now(),
log::EVENT::JOB_ADDITION,
OperatorName::MAX,
utils::SolutionIndicators<Route>(_input, _sol),
std::nullopt});
}
} while (job_added);
#endif
}

for (const auto v : modified_vehicles) {
_sol_state.update_route_bbox(_sol[v].route, v);
Expand Down Expand Up @@ -1886,16 +1804,14 @@ void LocalSearch<Route,

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

try_job_additions(best_ops[best_source][best_target]
->addition_candidates(),
0
recreate(best_ops[best_source][best_target]->addition_candidates()
#ifdef LOG_LS
,
true
,
true
#endif
);

Expand Down Expand Up @@ -1942,8 +1858,8 @@ void LocalSearch<Route,
for (auto req_u : best_ops[v][v]->required_unassigned()) {
if (!_sol_state.unassigned.contains(req_u)) {
// This move should be invalidated because a required
// unassigned job has been added by try_job_additions in
// the meantime.
// unassigned job has been added by recreate step in the
// meantime.
invalidate_move = true;
break;
}
Expand Down Expand Up @@ -2046,7 +1962,7 @@ void LocalSearch<Route,
_sol_state.setup(_sol);
}
#ifdef LOG_LS
if (_best_sol_indicators < current_sol_indicators or
if (_best_sol_indicators < current_sol_indicators ||
_best_sol_indicators == current_sol_indicators) {
steps.push_back({utils::now(),
log::EVENT::ROLLBACK,
Expand Down Expand Up @@ -2090,12 +2006,11 @@ void LocalSearch<Route,
_sol_state.set_insertion_ranks(_sol[v], v);
}

// Refill jobs.
constexpr double refill_regret = 1.5;
try_job_additions(_all_routes, refill_regret);
// Recreate solution
recreate(_all_routes);

// Update everything except what has already been updated in
// try_job_additions.
// recreate step.
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);
Expand Down
7 changes: 3 additions & 4 deletions src/algorithms/local_search/local_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,10 @@ class LocalSearch {
std::vector<log::Step<Route>> steps;
#endif

void try_job_additions(const std::vector<Index>& routes,
double regret_coeff
void recreate(const std::vector<Index>& routes
#ifdef LOG_LS
,
bool log_addition_step = false
,
bool log_addition_step = false
#endif
);

Expand Down
13 changes: 13 additions & 0 deletions src/structures/vroom/job.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,19 @@ All rights reserved (see LICENSE).
#include "structures/vroom/job.h"
#include "utils/helpers.h"

#include <numeric>

namespace vroom {

inline Duration get_tw_length(const std::vector<TimeWindow>& tws) {
return std::accumulate(tws.begin(),
tws.end(),
0,
[](auto sum, const auto& tw) {
return sum + tw.length;
});
};

Job::Job(Id id,
const Location& location,
UserDuration setup,
Expand All @@ -32,6 +43,7 @@ Job::Job(Id id,
skills(std::move(skills)),
priority(priority),
tws(tws),
tw_length(get_tw_length(tws)),
description(std::move(description)) {
utils::check_tws(tws, id, "job");
utils::check_priority(priority, id, "job");
Expand All @@ -57,6 +69,7 @@ Job::Job(Id id,
skills(std::move(skills)),
priority(priority),
tws(tws),
tw_length(get_tw_length(tws)),
description(std::move(description)) {
assert(type == JOB_TYPE::PICKUP || type == JOB_TYPE::DELIVERY);
utils::check_tws(tws, id, "job");
Expand Down
11 changes: 11 additions & 0 deletions src/structures/vroom/job.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct Job {
const Skills skills;
const Priority priority;
const std::vector<TimeWindow> tws;
const Duration tw_length;
const std::string description;

// Constructor for regular one-stop job (JOB_TYPE::SINGLE).
Expand Down Expand Up @@ -64,6 +65,16 @@ struct Job {
}

bool is_valid_start(Duration time) const;

friend bool operator<(const Job& lhs, const Job& rhs) {
// Sort by:
// - decreasing priority
// - increasing TW length
// - decreasing delivery amount
// - decreasing pickup amount
return std::tie(rhs.priority, lhs.tw_length, rhs.delivery, rhs.pickup) <
std::tie(lhs.priority, rhs.tw_length, lhs.delivery, lhs.pickup);
}
};

} // namespace vroom
Expand Down
Loading

0 comments on commit 81b2a95

Please sign in to comment.