Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Multiple profiles in the same problem #450

Merged
merged 72 commits into from
Apr 9, 2021
Merged

Conversation

jcoupey
Copy link
Collaborator

@jcoupey jcoupey commented Feb 3, 2021

Issue

Fixes #394.

Tasks

This PR aims at allowing several vehicle profiles in a single optimization, requiring different kind of changes to the codebase. In particular, all calls to travel times values should become vehicle-dependent. So on top of allowing several profiles, we could take the opportunity to introduce an additional scaling variable to allow fine-tuning travel times for vehicles with the same profile. Something along the line of a vehicle.speed_factor value, defaulting to 1, where a value of 1.1 would mean a faster vehicle and 0.9 a slower one.

Technical adjustments

  • List all vehicle profiles in input
  • Request and store a matrix per profile
  • Parallelize firing matrix requests
  • Error on wrong profile prior to firing any matrix request
  • Parse user-provided matrices based on profiles
  • Parse and store vehicle-dependant speed factors
  • Define a cost wrapper struct for vehicles
  • Replace all matrix access by a vehicle-dependent call
  • Use relevant values in heuristics
  • Use relevant values in LocalSearch::try_job_additions

Local search

Some of the local search operators currently implicitly rely on travel times being equal across vehicles. For example when exchanging the end of two routes (2-opt), we currently only evaluate gains for edges at the breakpoints, not gains related to the vehicle change for the route portions exchanged.

We should go through all operators to make gain evaluation more generic and handle the situation of different travel times between pairs of vehicles involved. As far as I can tell, this should not increase algorithmic complexity but will in some case require to store more data in SolutionState. E.g. for 2-opt, we'll probably have to store accumulated travel times up to (and from) any step for any route and any profile.

Currently existing operators are:

  • CrossExchange
    - [ ] Exchange
    - [ ] IntraCrossExchange
    - [ ] IntraExchange
    - [ ] IntraMixedExchange
    - [ ] IntraOrOpt
    - [ ] IntraRelocate
  • MixedExchange
  • OrOpt
    - [ ] PdShift
    - [ ] Relocate
  • ReverseTwoOpt
  • RouteExchange
  • TwoOpt
    - [ ] UnassignedExchange

EDIT: I crossed out the ones that should not require any change.

Heuristics changes

This is where I expect most of the real solving adjustments to happen. We can expect the local search to fix heuristic biases to some extent, but this requires to get heuristics solutions that are not too bad in the first place.

For example, seeding a route with the furthest task (INIT::FURTHEST) or the more demanding wrt capacity (HIGHER_AMOUNT) does not make sense when designing a route for a slow and small vehicle, especially if bigger and faster vehicles are in line for other routes down the line. Also the whole "regret" logic for costs (borrowed from Solomon) is likely to work in odd ways with different vehicle travel times.

So maybe this will require working on vehicle ordering prior to building routes, maybe this will mean adjusting the seeding approach, probably this will require to adjust the combination of parameters used by the heuristics.

  • Evaluate current heuristics behavior
  • Generate/use typical instances to analyse heuristics biases
    - [ ] Adjust heuristics to avoid the most obvious biases
    - [ ] Reset parameters tuning based on changes?

Usual PR tasks

  • Evaluate impact on non-multi-profile instances
  • Update docs/API.md
  • Update CHANGELOG.md
  • review

@jcoupey jcoupey self-assigned this Feb 4, 2021
@jcoupey
Copy link
Collaborator Author

jcoupey commented Feb 6, 2021

Last build is actually OK, Travis only reports a failure because of example_2.json containing a custom matrix that is no longer parsed due to ongoing changes.

@jcoupey
Copy link
Collaborator Author

jcoupey commented Feb 8, 2021

A new way to handle some of the computed matrices is required to some extent. The simple cases matching previous behavior are:

  1. providing matrices for all profiles along with location_index values everywhere. This is fine as users decide of the matrix layout.
  2. providing no matrix at all. This is fine too as long as used profiles have matching routing servers configured: in that case indices of locations in the matrix internally depend on the order of location addition in input.

Kind of an edge case now: providing some profile matrices but not all. We should actually allow this as long as missing matrices match a valid profile with a routing server configured. The tricky part is that then users do choose the layout with (mandatory) location_index keys but based on location input ordering we compute a different matrix, even maybe a smaller one if location_index values are sparse. So I ended up adding a level of indirection here: we start by computing the usual matrix based on input order then move the values to the final matrix where they are expected based on location_index values.

@jcoupey jcoupey force-pushed the feature/multi-profile branch from ab613d1 to b8f2a1a Compare March 2, 2021 13:36
@jcoupey
Copy link
Collaborator Author

jcoupey commented Mar 2, 2021

This PR is now in a alpha state, meaning:

  • API changes are done and covered in the docs for multiple profiles and vehicles speed_factor keys;
  • solutions in output are consistent with the new input;
  • the right vehicle dependent travel times/costs are used throughout solving (heuristics for initial solutions and the whole local search process).

In short the feature should be functional in term of I/O and profile/travel-times consistency but I'd still expect we produce quite sub-optimal solutions depending on the use-case.

There is no change on single-profile instances AFAICT, except for an increase in computing time introduced by the profile-handling overhead (we'll have to evaluate this at some point).

Now for the fun part, I'll start working on actual multi-profile instances to try and improve the solving.

@jcoupey
Copy link
Collaborator Author

jcoupey commented Mar 5, 2021

For the record there are still things to improve for the "technical" part as well. I spotted situations where we hit this assertion due to rounding subtleties with speed_factor values. Will investigate and find a way to circumvent the problem.

@jcoupey
Copy link
Collaborator Author

jcoupey commented Apr 8, 2021

After quite some benchmarking, I'm pretty confident the current setup works fine in most cases. Yet the concerns raised in the initial sketch about heuristics behavior are still somehow pending.

Example of a open question: do we need to take into account new speed discrepancies to order vehicles in the BASIC heuristic? The current ordering logic does work fine enough in most situations but the answer is probably instance-dependent.

On the other hand, this kind of concern/question is stressed by this PR but the actual scope is broader: we already perform some ordering choices based on vehicle capacity or working hours length that are arguably questionable in the same way.

So I plan to include this whole topic in a dedicated ticket at some point and merge this PR without altering the overall heuristic logic, as it's already a massive change.

@jcoupey
Copy link
Collaborator Author

jcoupey commented Apr 9, 2021

I've been evaluating impact of this PR on single-profile instances. After the last commit that fixed a heuristic behavior change, I checked that this PR and current master provide exactly the same results on the usual CVRP + VRPTW benchmarks.

On those benchmarks the computing times are steadily increasing by around 17%. This is solely due to the overhead introduced to access travel times: we now have a function call wrapping the speed_factor-based computing, instead of a direct matrix access previously. I don't think there is a way around that so we'll have to live with this for the sake of the feature/modeling flexibility introduced by this PR.

I've tried my best to make the changes compiler-friendly but maybe we still have some room for improvement there. This would require a closer look so I'll ticket that as a follow-up work after merging.

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Vehicle-dependent travel times
1 participant