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

Scheduling move checks to find best index #227

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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@

### Changed

- Improve cases where a service has two visits in periodic heuristic: ensure that the second visit can be assigned to the right day [#227](https://github.com/Mapotempo/optimizer-api/pull/227)

### Removed

- Field `trips` in vehicle model. Use `vehicle_trips` relation instead [#123](https://github.com/Mapotempo/optimizer-api/pull/123)

### Fixed

- VROOM was used incorrectly in various cases: negative quantities, vehicle duration, activity position [#223](https://github.com/Mapotempo/optimizer-api/pull/223) [#242](https://github.com/Mapotempo/optimizer-api/pull/242)
- Capacity violation in periodic heuristic algorithm (`first_solution_strategy='periodic'`) [#227](https://github.com/Mapotempo/optimizer-api/pull/227)

## [v1.7.1] - 2021-05-20

Expand Down
2 changes: 1 addition & 1 deletion lib/heuristics/concerns/periodic_data_initialisation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def initialize_routes(routes)
defined_route.mission_ids.each{ |id|
next if !@services_data.has_key?(id) # id has been removed when detecting unfeasible services in wrapper

best_index = find_best_index(id, associated_route) if associated_route
best_index = find_best_index(id, associated_route, false) if associated_route
considered_ids << id
if best_index
insert_point_in_route(associated_route, best_index, false)
Expand Down
67 changes: 21 additions & 46 deletions lib/heuristics/concerns/periodic_end_phase.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ def refine_solution(&block)
end
end

def days_respecting_lapse(id, vehicle_id)
def days_respecting_lapse(id, vehicle_routes)
min_lapse = @services_data[id][:raw].minimum_lapse
max_lapse = @services_data[id][:raw].maximum_lapse
used_days = @services_data[id][:used_days]

return @candidate_routes[vehicle_id].keys if used_days.empty?
return vehicle_routes.keys if used_days.empty?

@candidate_routes[vehicle_id].keys.select{ |day|
vehicle_routes.keys.select{ |day|
smaller_lapse_with_other_days = used_days.collect{ |used_day| (used_day - day).abs }.min
(min_lapse.nil? || smaller_lapse_with_other_days >= min_lapse) &&
(max_lapse.nil? || smaller_lapse_with_other_days <= max_lapse)
Expand Down Expand Up @@ -99,15 +99,15 @@ def add_missing_visits
until costs.empty?
# select best visit to insert
max_priority = costs.keys.collect{ |id| @services_data[id][:raw].priority + 1 }.max
best_cost = costs.min_by{ |id, info| ((@services_data[id][:raw].priority.to_f + 1) / max_priority) * (info[:cost][:additional_route_time] / @services_data[id][:raw].visits_number**2) }
best_cost = costs.min_by{ |id, info| ((@services_data[id][:raw].priority.to_f + 1) / max_priority) * (info[:additional_route_time] / @services_data[id][:raw].visits_number**2) }

id = best_cost[0]
day = best_cost[1][:day]
vehicle_id = best_cost[1][:vehicle]
log "It is interesting to add #{id} at day #{day} on #{vehicle_id}", level: :debug

@ids_to_renumber |= [id]
insert_point_in_route(@candidate_routes[vehicle_id][day], best_cost[1][:cost])
insert_point_in_route(@candidate_routes[vehicle_id][day], best_cost[1])
@output_tool&.add_single_visit(day, @services_data[id][:used_days], id, @services_data[id][:raw].visits_number)

costs = update_costs(costs, best_cost)
Expand All @@ -121,13 +121,12 @@ def update_costs(costs, best_cost)
uninserted_set = @uninserted.select{ |_key, info| info[:original_id] == best_cost[0] }.keys
@uninserted.delete(uninserted_set.first)
if uninserted_set.size > 1
available_days = days_respecting_lapse(best_cost[0], best_cost[1][:vehicle])
available_days = days_respecting_lapse(best_cost[0], @candidate_routes[best_cost[1][:vehicle]])

day, cost = find_best_day_cost(available_days, @candidate_routes[best_cost[1][:vehicle]], best_cost[0])
cost = find_best_day_cost(@candidate_routes[best_cost[1][:vehicle]], best_cost[0], available_days)

if cost
costs[best_cost[0]][:day] = day
costs[best_cost[0]][:cost] = cost
costs[best_cost[0]] = cost
else
costs.delete(best_cost[0])
end
Expand All @@ -139,14 +138,10 @@ def update_costs(costs, best_cost)
costs.each{ |id, info|
next if info[:day] != best_cost[1][:day] && info[:vehicle] != best_cost[1][:vehicle]

day, cost = find_best_cost(id, info[:vehicle])
cost = find_best_day_cost(@candidate_routes[info[:vehicle]], id)

if cost
costs[id] = {
day: day,
vehicle: info[:vehicle],
cost: cost
}
costs[id] = cost
else
costs.delete(id)
end
Expand All @@ -155,48 +150,28 @@ def update_costs(costs, best_cost)
costs
end

def find_best_day_cost(available_days, vehicle_routes, id)
return [nil, nil] if available_days.empty?
def find_best_day_cost(vehicle_routes, id, available_days = nil)
available_days ||= days_respecting_lapse(id, vehicle_routes)

day = available_days[0]
if @same_point_day && @services_data[id][:group_capacity].all?{ |need, quantity| quantity <= vehicle_routes[day][:capacity_left][need] } ||
!@same_point_day && @services_data[id][:capacity].all?{ |need, quantity| quantity <= vehicle_routes[day][:capacity_left][need] }
cost = find_best_index(id, vehicle_routes[day])
end
return [nil, nil] unless available_days.any?

index = 1
index = 0
cost = nil
while cost.nil? && index < available_days.size
day = available_days[index]

if @same_point_day && @services_data[id][:group_capacity].all?{ |need, quantity| quantity <= vehicle_routes[day][:capacity_left][need] } ||
!@same_point_day && @services_data[id][:capacity].all?{ |need, quantity| quantity <= vehicle_routes[day][:capacity_left][need] }
cost = find_best_index(id, vehicle_routes[day])
end
cost = find_best_index(id, vehicle_routes[available_days[index]], false)
index += 1
end

[day, cost]
end

def find_best_cost(id, vehicle_id)
available_days = days_respecting_lapse(id, vehicle_id)
find_best_day_cost(available_days, @candidate_routes[vehicle_id], id)
cost
end

def compute_first_costs
costs = {}

@missing_visits.collect{ |vehicle, list|
list.collect{ |service_id|
day, cost = find_best_cost(service_id, vehicle)

next if cost.nil?

costs[service_id] = {
day: day,
vehicle: vehicle,
cost: cost
}
cost = find_best_day_cost(@candidate_routes[vehicle], service_id)
costs[service_id] = cost if cost
}
}

Expand Down Expand Up @@ -308,7 +283,7 @@ def reaffect_in_non_empty_route(still_removed)
referent_route ||= route_data
insertion_costs = compute_costs_for_route(route_data, remaining_ids)
insertion_costs.each{ |cost|
cost[:vehicle] = vehicle
cost[:vehicle] = vehicle_id
cost[:day] = day
}
insertion_costs
Expand All @@ -332,7 +307,7 @@ def reaffect_in_non_empty_route(still_removed)
insert_point_in_route(@candidate_routes[point_to_add[:vehicle]][point_to_add[:day]], point_to_add, false)
@output_tool&.add_single_visit(point_to_add[:day], @services_data[point_to_add[:id]][:used_days], point_to_add[:id], @services_data[point_to_add[:id]][:raw].visits_number)
still_removed.delete(still_removed.find{ |removed| removed.first == point_to_add[:id] })
@uninserted.delete(@uninserted.find{ |_id, data| data[:original_id] == to_plan[:service] }[0])
@uninserted.delete(@uninserted.find{ |_id, data| data[:original_id] == point_to_add[:id] }[0])
end
end

Expand Down
Loading