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

Revert recreation changes #1207

Merged
merged 2 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
- Refactor heuristics to reduce code duplication (#1181)
- Refactor `Matrix` template class (#1089)
- Refactor to use `std::format` whenever possible (#1081)
- Reduce complexity for recreation process (#1155)
- Refactor `SolutionIndicators` (#1169)
- Remove amount consistency checks in `parse` in favor of upstream checks in `Input` (#1086)
- Reduce code duplication in routing wrappers (#1184)
Expand Down
237 changes: 161 additions & 76 deletions src/algorithms/local_search/local_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,98 +161,180 @@ void LocalSearch<Route,
SwapStar,
RouteSplit,
PriorityReplace,
TSPFix>::recreate(const std::vector<Index>& routes
TSPFix>::try_job_additions(const std::vector<Index>& routes,
double regret_coeff
#ifdef LOG_LS
,
bool log_addition_step
,
bool log_addition_step
#endif
) {
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];
});
bool job_added;

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

for (const auto j : ordered_jobs) {
const auto& current_job = _input.jobs[j];
if (current_job.type == JOB_TYPE::DELIVERY) {
continue;
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]);

if (route_job_insertions[i][j].eval != NO_EVAL) {
route_job_insertions[i][j].eval.cost += fixed_cost;
}
}
}

std::unordered_set<Index> modified_vehicles;

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

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) {
for (const auto j : _sol_state.unassigned) {
const auto& current_job = _input.jobs[j];
if (current_job.type == JOB_TYPE::DELIVERY) {
continue;
}

auto current_best_insertion =
compute_best_insertion(_input, _sol_state, j, v, _sol[v]);
const auto job_priority = current_job.priority;

if (current_best_insertion.eval != NO_EVAL && _sol[v].empty()) {
current_best_insertion.eval.cost += _input.vehicles[v].fixed_cost();
if (job_priority < best_priority) {
// Insert higher priority jobs first.
continue;
}

if (current_best_insertion.eval < best_insertion.eval) {
best_insertion = current_best_insertion;
best_v = 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 (best_insertion.eval == NO_EVAL) {
// Current job cannot be inserted.
continue;
}
// 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;
}

// Found a valid insertion spot for current job.
_sol_state.unassigned.erase(j);
const auto& current_r = _sol[routes[i]];
const auto& vehicle = _input.vehicles[routes[i]];

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

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 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;
}
}
}

// 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);
job_added = (best_cost < std::numeric_limits<double>::max());

if (job_added) {
_sol_state.unassigned.erase(best_job_rank);

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);
}

// 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);

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;
}
}

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

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

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

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

Expand Down Expand Up @@ -1859,8 +1943,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 recreate step in the
// meantime.
// unassigned job has been added by try_job_additions in
// the meantime.
invalidate_move = true;
break;
}
Expand Down Expand Up @@ -2003,11 +2087,12 @@ void LocalSearch<Route,
_sol_state.set_insertion_ranks(_sol[v], v);
}

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

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

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

Expand Down
13 changes: 0 additions & 13 deletions src/structures/vroom/job.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,8 @@ 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 @@ -43,7 +32,6 @@ 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 @@ -69,7 +57,6 @@ 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: 0 additions & 11 deletions src/structures/vroom/job.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ 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 @@ -65,16 +64,6 @@ 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