Skip to content

Commit

Permalink
Merge pull request #159 from senhalil/feat/improve-optim-insert-for-l…
Browse files Browse the repository at this point in the history
…ow-quality-first-solutions

Improve optim insert for low quality first solutions
  • Loading branch information
fab-girard authored Apr 6, 2021
2 parents 3a6a3bd + 3acc001 commit 75f96e8
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Bump VROOM to v1.8.0 and start using the features integrated since v1.3.0 [#107](https://github.com/Mapotempo/optimizer-api/pull/107)
- Bump OR-Tools v7.8 [#107](https://github.com/Mapotempo/optimizer-api/pull/107)
- VROOM were previously always called synchronously, it is now reserved to a set of effective `router_mode` (:car, :truck_medium) within a limit of points (<200). [#107](https://github.com/Mapotempo/optimizer-api/pull/107)
- Heuristic selection (`first_solution_strategy='self_selection'`) takes into account the supplied initial routes (`routes`) and the best solution is used as the initial route [#159](https://github.com/Mapotempo/optimizer-api/pull/159)

### Removed

Expand Down
67 changes: 44 additions & 23 deletions lib/interpreters/compute_several_solutions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,16 @@ def self.collect_heuristics(vrp, first_solution_strategy)
if first_solution_strategy.first == 'self_selection'
mandatory_heuristic = select_best_heuristic(vrp)

heuristic_list = if vrp[:vehicles].any?{ |vehicle| vehicle[:force_start] || vehicle[:shift_preference] && vehicle[:shift_preference] == 'force_start' }
[mandatory_heuristic, verified('local_cheapest_insertion'), verified('global_cheapest_arc')]
elsif mandatory_heuristic == 'savings'
[mandatory_heuristic, verified('global_cheapest_arc'), verified('local_cheapest_insertion')]
elsif mandatory_heuristic == 'parallel_cheapest_insertion'
[mandatory_heuristic, verified('global_cheapest_arc'), verified('local_cheapest_insertion')]
else
[mandatory_heuristic]
end
heuristic_list =
if vrp[:vehicles].any?{ |vehicle| vehicle[:force_start] || vehicle[:shift_preference].to_s == 'force_start' }
[mandatory_heuristic, verified('local_cheapest_insertion'), verified('global_cheapest_arc')]
elsif mandatory_heuristic == 'savings'
[mandatory_heuristic, verified('global_cheapest_arc'), verified('local_cheapest_insertion')]
elsif mandatory_heuristic == 'parallel_cheapest_insertion'
[mandatory_heuristic, verified('global_cheapest_arc'), verified('local_cheapest_insertion')]
else
[mandatory_heuristic]
end

heuristic_list |= ['savings'] if vrp.vehicles.collect{ |vehicle| vehicle[:rests].to_a.size }.sum.positive? # while waiting for self_selection improve
heuristic_list
Expand Down Expand Up @@ -161,20 +162,30 @@ def self.several_solutions(service_vrps)

def self.find_best_heuristic(service_vrp)
vrp = service_vrp[:vrp]
strategies = vrp.preprocessing_first_solution_strategy
custom_heuristics = collect_heuristics(vrp, strategies)
custom_heuristics = collect_heuristics(vrp, vrp.preprocessing_first_solution_strategy)
if custom_heuristics.size > 1
log '---> find_best_heuristic'
tic = Time.now
percent_allocated_to_heur_selection = 0.3 # spend at most 30% of the total time for heuristic selection
total_time_allocated_for_heuristic_selection = service_vrp[:vrp].resolution_duration.to_f * percent_allocated_to_heur_selection
batched_service_vrps = batch_heuristic([service_vrp], custom_heuristics).flatten(1)
time_for_each_heuristic = (total_time_allocated_for_heuristic_selection / custom_heuristics.size).to_i

custom_heuristics << 'supplied_initial_routes' if vrp.routes.any?

times = []
first_results = batched_service_vrps.collect{ |s_vrp|
first_results = custom_heuristics.collect{ |heuristic|
s_vrp = Marshal.load(Marshal.dump(service_vrp))
if heuristic == 'supplied_initial_routes'
s_vrp[:vrp].preprocessing_first_solution_strategy = [verified('global_cheapest_arc')] # fastest for fallback
else
s_vrp[:vrp].routes = []
s_vrp[:vrp].preprocessing_first_solution_strategy = [verified(heuristic)]
end
s_vrp[:vrp].restitution_allow_empty_result = true
s_vrp[:vrp].resolution_batch_heuristic = true
s_vrp[:vrp].resolution_initial_time_out = nil
s_vrp[:vrp].resolution_minimum_duration = nil
s_vrp[:vrp].resolution_duration = [(total_time_allocated_for_heuristic_selection / custom_heuristics.size).to_i, 300000].min # do not spend more than 5 min for a single heuristic
s_vrp[:vrp].resolution_duration = [time_for_each_heuristic, 300000].min # no more than 5 min for single heur
heuristic_solution = OptimizerWrapper.solve(s_vrp)
times << (heuristic_solution && heuristic_solution[:elapsed] || 0)
heuristic_solution
Expand All @@ -185,30 +196,40 @@ def self.find_best_heuristic(service_vrp)
synthesis = []
first_results.each_with_index{ |result, i|
synthesis << {
heuristic: batched_service_vrps[i][:vrp].preprocessing_first_solution_strategy.first,
quality: result.nil? ? nil : result[:cost].to_i + (times[i] / 1000).to_i,
heuristic: custom_heuristics[i],
quality: result.nil? ? Float::MAX : result[:cost].to_i + (times[i] / 1000).to_i,
used: false,
cost: result ? result[:cost] : nil,
time_spent: times[i],
solution: result
}
}
sorted_heuristics = synthesis.sort_by{ |element| element[:quality].nil? ? synthesis.collect{ |data| data[:quality] }.compact.max * 10 : element[:quality] }

best_heuristic = sorted_heuristics[0][:heuristic]
best = synthesis.min_by{ |element| element[:quality] }

if best[:heuristic] != 'supplied_initial_routes'
# if another heuristic is the best, use its solution as the initial route
vrp.routes = best[:solution][:routes].collect{ |route|
mission_ids = route[:activities].collect{ |a|
a[:service_id] || a[:pickup_shipment_id] || a[:delivery_shipment_id]
}.compact
next if mission_ids.empty?

Models::Route.new(vehicle: vrp.vehicles.find{ |v| v[:id] = route[:vehicle_id] }, mission_ids: mission_ids)
}.compact
end

synthesis.find{ |heur| heur[:heuristic] == best_heuristic }[:used] = true
best[:used] = true

vrp.preprocessing_heuristic_result = synthesis.find{ |heur| heur[:heuristic] == best_heuristic }[:solution]
vrp.preprocessing_heuristic_result = best[:solution]
vrp.preprocessing_heuristic_result[:solvers].each{ |solver|
solver = 'preprocessing_' + solver
}
synthesis.each{ |synth| synth.delete(:solution) }
vrp.resolution_batch_heuristic = nil
vrp.preprocessing_first_solution_strategy = [best_heuristic]
vrp.preprocessing_first_solution_strategy = [best[:heuristic]]
vrp.preprocessing_heuristic_synthesis = synthesis
vrp.resolution_duration = vrp.resolution_duration ? [(vrp.resolution_duration.to_f * (1 - percent_allocated_to_heur_selection)).round, 1000].max : nil
log "<--- find_best_heuristic elapsed: #{Time.now - tic}sec selected heuristic: #{best_heuristic}"
log "<--- find_best_heuristic elapsed: #{Time.now - tic}sec selected heuristic: #{best[:heuristic]}"
else
vrp.preprocessing_first_solution_strategy = custom_heuristics
end
Expand Down

0 comments on commit 75f96e8

Please sign in to comment.