-
Notifications
You must be signed in to change notification settings - Fork 378
Mixed pickups and deliveries #262
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
Conversation
On second thoughts, we can't just keep the old Example: say we have a delivery of Solution: add two |
More on point 4 from previous comment. I've been profiling executions with I'm puzzled by:
The place where the latest start showing off is at f16aa71, the first check update: $ git diff f16aa71~ f16aa71
diff --git a/src/problems/cvrp/operators/exchange.cpp b/src/problems/cvrp/operators/exchange.cpp
index 4f2a611..726a150 100644
--- a/src/problems/cvrp/operators/exchange.cpp
+++ b/src/problems/cvrp/operators/exchange.cpp
@@ -115,13 +115,19 @@ bool Exchange::is_valid() {
bool valid = _input.vehicle_ok_with_job(t_vehicle, s_job_rank);
valid &= _input.vehicle_ok_with_job(s_vehicle, t_job_rank);
- valid &= (_sol_state.fwd_amounts[t_vehicle].back() -
- _input.jobs[t_job_rank].amount + _input.jobs[s_job_rank].amount <=
- _input.vehicles[t_vehicle].capacity);
-
- valid &= (_sol_state.fwd_amounts[s_vehicle].back() -
- _input.jobs[s_job_rank].amount + _input.jobs[t_job_rank].amount <=
- _input.vehicles[s_vehicle].capacity);
+ valid &=
+ target.is_valid_addition_for_capacity(_input,
+ _input.jobs[s_route[s_rank]].pickup,
+ _input.jobs[s_route[s_rank]].delivery,
+ t_rank,
+ t_rank + 1);
+
+ valid &=
+ source.is_valid_addition_for_capacity(_input,
+ _input.jobs[t_route[t_rank]].pickup,
+ _input.jobs[t_route[t_rank]].delivery,
+ s_rank,
+ s_rank + 1);
return valid;
} @krypt-n I suspect this might be related to the way I've been working with amounts for validity checks, any idea? |
The 5-argument bool RawRoute::is_valid_addition_for_capacity_margins(
const Input& input,
const Amount& pickup,
const Amount& delivery,
const Index first_rank,
const Index last_rank) const {
assert(1 <= last_rank);
assert(last_rank <= route.size() + 1);
auto first_deliveries =
(first_rank == 0) ? current_loads[0] : bwd_deliveries[first_rank - 1];
auto first_pickups = (first_rank == 0) ? Amount(input.amount_size())
: fwd_pickups[first_rank - 1];
auto replaced_deliveries = first_deliveries - bwd_deliveries[last_rank - 1];
return (fwd_peaks[first_rank] + delivery <=
capacity + replaced_deliveries) and
(bwd_peaks[last_rank] + pickup <=
capacity + fwd_pickups[last_rank - 1] - first_pickups);
} I'd suggest the following changes which get rid of the allocations that are noticeable in the flamegraph:
The declaration The other Hope that helps |
@krypt-n thanks a lot for your input, I've been able to reduce allocations following your suggestions. I still see some in the flame graph for the last commit but this is probably related to this |
The last few commits to address #266 are really promising on the computing times side. Average computing time on
|
Issue
The scope of this PR is to implement support for mixing independent pickups and deliveries in a single problem (#241). It should also fix #197 along the way.
Here "independent" stands for "no precedence constraints involved" (as opposed to the discussion in #189). So this is "just" a matter of making sure all our capacity checks handle mixed scenarios.
API
No big change required, amounts are already defined using
Capacity
which is an alias forint64_t
. We should just make it clear that deliveries/pickups for jobs can be modeled using positive/negative capacities. Maybe just enforce that vehicle capacity values are unsigned integers. And perhaps report vehicle load at step level in output.EDIT : #262 (comment)
Data changes
Modelling
If capacities can be positive/negative in input, the easiest way is probably to split pickups and deliveries at job level. This could be achieved by replacing the current
Job::amount
by two membersJob::delivery
andJob::pickup
.Example: a job amount of
[4, 6, -2]
in input would result in a[4, 6, 0]
job delivery and a[0, 0, 2]
pickup at theJob
object level.EDIT : #262 (comment)
Maintaining relevant information
The crucial part here to avoid performance degradation is to keep constant-time lookups for all capacity checks. So going through a whole route part to decide if adding a job will break something before or after is out of question. We should be able to achieve this by storing relevant information upon route modifications (those do not happen very often compared to validity checks).
Say we have a route
s -> j_1 -> j_2 -> ... -> j_i -> ... -> j_n -> e
and pickup (resp. delivery) amount for jobj_i
isp_i
(resp.d_i
).Π_i = sum(p_k) for 1 <= k <= i
(all pickups up toj_i
in the route)Δ_i = sum(d_k) for i < k <= n
(all pending deliveries afterj_i
)j_i
is thenL_i = Π_i + Δ_i
j_i
isFL_i = max(L_k) for k <= i
j_i
isBL_i = max(L_k) for i <= k <= n
This is quite similar to what is currently stored in
SolutionState::fwd_amounts
andSolutionState::bwd_amounts
, which are updated upon route modification throughSolutionState::update_amounts
. Warning:LocalSearch::try_job_additions
currently has its own logic to update those values, it should be replaced to use the same function.Validity checks
Additions
Suppose we want to check whether it is OK to include:
D
and pickupP
(be it a job or bundle of jobs)i
andi + 1
C
Then we have to check for
BL_{i + 1} + P <= C && FL_i + D <= C
.Removal
Should always be OK.
Tasks
delivery
andpickup
members for jobstry_job_addition
to useSolutionState::update_amounts
SolutionState
toRawRoute
RawRoute
members.- [ ] Adjust all capacity checks in clustering heuristicstry_job_additions
capacity checks- [ ] Fall back to simpler validity checks for delivery-only or pickup-only instances- [ ] Update 2-opt early abort checkspickup
anddelivery
keys at job levelamount
keys in both input and outputdocs/API.md
CHANGELOG.md