Skip to content

Commit

Permalink
Step -> Stop
Browse files Browse the repository at this point in the history
  • Loading branch information
Braktar committed Nov 15, 2021
1 parent 66ede99 commit 3c299ee
Show file tree
Hide file tree
Showing 34 changed files with 677 additions and 676 deletions.
30 changes: 15 additions & 15 deletions lib/cleanse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,33 @@ def self.cleanse_empties_fills(vrp, solution)
capacities_units = vehicle.capacities.collect{ |capacity| capacity.unit_id if capacity.limit }.compact
previous_activity = nil

route.steps.delete_if{ |step|
next unless service_types.include?(step.type)
route.stops.delete_if{ |stop|
next unless service_types.include?(stop.type)

if previous_activity && step && same_position(vrp, step) &&
same_empty_units(capacities_units, previous_activity, step) &&
!same_fill_units(capacities_units, previous_activity, step)
add_unnassigned(solution.unassigned, step, 'Duplicate empty service.')
if previous_activity && stop && same_position(vrp, stop) &&
same_empty_units(capacities_units, previous_activity, stop) &&
!same_fill_units(capacities_units, previous_activity, stop)
add_unnassigned(solution.unassigned, stop, 'Duplicate empty service.')
true
elsif previous_activity && step && same_position(vrp, step) &&
same_fill_units(capacities_units, previous_activity, step) &&
!same_empty_units(capacities_units, previous_activity, step)
add_unnassigned(solution.unassigned, step, 'Duplicate fill service.')
elsif previous_activity && stop && same_position(vrp, stop) &&
same_fill_units(capacities_units, previous_activity, stop) &&
!same_empty_units(capacities_units, previous_activity, stop)
add_unnassigned(solution.unassigned, stop, 'Duplicate fill service.')
true
else
previous_activity = step if previous_activity.nil? || step.service_id
previous_activity = stop if previous_activity.nil? || stop.service_id
false
end
}
}
end

def self.add_unnassigned(unassigned, route_step, reason)
route_step.reason = reason
unassigned << route_step
def self.add_unnassigned(unassigned, route_stop, reason)
route_stop.reason = reason
unassigned << route_stop
end

def self.cleanse_empty_routes(result)
result.routes.delete_if{ |route| route.steps.none?(&:service_id) }
result.routes.delete_if{ |route| route.stops.none?(&:service_id) }
end
end
26 changes: 13 additions & 13 deletions lib/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def self.replace_routes_in_result(solution, new_solution)
# TODO: Correct total cost (needs cost per route!!!)

# Correct unassigned services
new_service_ids = new_solution.routes.flat_map{ |route| route.steps.map(&:service_id) }.compact +
new_service_ids = new_solution.routes.flat_map{ |route| route.stops.map(&:service_id) }.compact +
new_solution.unassigned.map(&:service_id).compact

solution.unassigned.delete_if{ |activity|
Expand All @@ -157,7 +157,7 @@ def self.replace_routes_in_result(solution, new_solution)
new_solution.routes.each{ |new_route|
# This vehicle might not be used in the old solutions
old_index = solution.routes.index{ |r| r.vehicle.id == new_route.vehicle.id }
# old_route_steps = old_index ? solution.routes[old_index].steps : []
# old_route_stops = old_index ? solution.routes[old_index].stops : []
solution.routes[old_index] = new_route if old_index
solution.routes << new_route unless old_index

Expand Down Expand Up @@ -243,12 +243,12 @@ def round_to_multiple_of(num)
(self / num).round(6).round(0) * num # .round(6).round(0) to prevent floating point errors
end

# rounds the number to the closests step in between [val.round(ndigits), val.round(ndigits) + 1/10**ndigits].
# rounds the number to the closests stop in between [val.round(ndigits), val.round(ndigits) + 1/10**ndigits].
# Useful when rounding is performed to reduce the number of uniq elements.
#
# ndigits: the number of decimal places (when nsteps = 0)
# ndigits: the number of decimal places (when nstops = 0)
#
# nsteps: the number of steps between val.round(ndigits) and val.round(ndigits) + 1/10**ndigits
# nstops: the number of stops between val.round(ndigits) and val.round(ndigits) + 1/10**ndigits
#
# For example,
# array = [0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2]
Expand All @@ -257,17 +257,17 @@ def round_to_multiple_of(num)
# array.collect{ |val| val.round(1) }.uniq :=> [0.1, 0.2] # too little
# array.collect{ |val| val.round(2) }.uniq :=> [0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2] # too much
#
# With nsteps the number of uniq elements between two decimals can be controlled:
# With nstops the number of uniq elements between two decimals can be controlled:
# array.collect{ |val| val.round_with_steps(1, 0) }.uniq :=> [0.1, 0.2] # same with round(1)
# array.collect{ |val| val.round_with_steps(1, 1) }.uniq :=> [0.1, 0.15, 0.2] # one extra step in between
# array.collect{ |val| val.round_with_steps(1, 2) }.uniq :=> [0.1, 0.13, 0.17, 0.2] # two extra step in between
# array.collect{ |val| val.round_with_steps(1, 1) }.uniq :=> [0.1, 0.15, 0.2] # one extra stop in between
# array.collect{ |val| val.round_with_steps(1, 2) }.uniq :=> [0.1, 0.13, 0.17, 0.2] # two extra stop in between
# ...
# array.collect{ |val| val.round_with_steps(1, 8) }.uniq :=> [0.1, 0.11, 0.12, 0.13, 0.14, 0.16, 0.17, 0.18, 0.19, 0.2] # eigth extra step in between
# array.collect{ |val| val.round_with_steps(1, 8) }.uniq :=> [0.1, 0.11, 0.12, 0.13, 0.14, 0.16, 0.17, 0.18, 0.19, 0.2] # eigth extra stop in between
# array.collect{ |val| val.round_with_steps(1, 9) }.uniq :=> [0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2] # same with round(2)
#
# Theoretically, val.round_with_steps(ndigits, nsteps) is
# equivalent to val.round_to_multiple_of(1.0 / (nsteps + 1) / 10**ndigits )
def round_with_steps(ndigits, nsteps = 0)
self.round_to_multiple_of(1.fdiv((nsteps + 1) * 10**ndigits)).round(ndigits + 1) # same as ((self * (nsteps + 1.0)).round(ndigits) / (nsteps + 1.0)).round(ndigits + 1)
# Theoretically, val.round_with_steps(ndigits, nstops) is
# equivalent to val.round_to_multiple_of(1.0 / (nstops + 1) / 10**ndigits )
def round_with_steps(ndigits, nstops = 0)
self.round_to_multiple_of(1.fdiv((nstops + 1) * 10**ndigits)).round(ndigits + 1) # same as ((self * (nstops + 1.0)).round(ndigits) / (nstops + 1.0)).round(ndigits + 1)
end
end
11 changes: 6 additions & 5 deletions lib/heuristics/dichotomious_approach.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def self.dichotomious_heuristic(service_vrp, job = nil, &block)
end
solution.parse(vrp)


log "dicho - level(#{service_vrp[:dicho_level]}) unassigned rate " \
"#{solution.unassigned.size}/#{service_vrp[:vrp].services.size}: " \
"#{(solution.unassigned.size.to_f / service_vrp[:vrp].services.size * 100).round(1)}%"
Expand All @@ -152,7 +153,7 @@ def self.transfer_unused_vehicles(service_vrp, solution, sub_service_vrps)
sv_zero.vehicles.each{ |vehicle|
route = solution.routes.find{ |r| r.vehicle.id == vehicle.id }

next if route&.steps&.any?(&:service_id)
next if route&.stops&.any?(&:service_id)

sv_one.vehicles << vehicle
sv_zero.vehicles -= [vehicle]
Expand Down Expand Up @@ -260,7 +261,7 @@ def self.build_initial_routes(solutions)
next if solution.nil?

solution.routes.map{ |route|
mission_ids = route.steps.map(&:service_id).compact
mission_ids = route.stops.map(&:service_id).compact
next if mission_ids.empty?

Models::Route.create(
Expand All @@ -274,7 +275,7 @@ def self.build_initial_routes(solutions)
def self.remove_bad_skills(service_vrp, solution)
log '---> remove_bad_skills', level: :debug
solution.routes.each{ |r|
r.steps.each{ |a|
r.stops.each{ |a|
next unless a.service_id

service = service_vrp[:vrp].services.find{ |s| s.id == a.service_id }
Expand All @@ -284,7 +285,7 @@ def self.remove_bad_skills(service_vrp, solution)

log "dicho - removed service #{a.service_id} from vehicle #{r.vehicle.id}"
solution.unassigned << a
r.steps.delete(a)
r.stops.delete(a)
# TODO: remove bad sticky?
}
}
Expand Down Expand Up @@ -360,7 +361,7 @@ def self.insert_unassigned_by_skills(service_vrp, services, skills, solution, jo
end

assigned_service_ids = vehicles_indices.map{ |_v, r_i, _v_i| r_i }.compact.flat_map{ |r_i|
solution.routes[r_i].steps.map(&:service_id)
solution.routes[r_i].stops.map(&:service_id)
}.compact

sub_service_vrp = SplitClustering.build_partial_service_vrp(service_vrp,
Expand Down
34 changes: 17 additions & 17 deletions lib/heuristics/periodic_heuristic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ def collect_unassigned
data[:unassigned_indices].each{ |index|
service_in_vrp = @services_data[id][:raw]
unassigned_id = "#{id}_#{index}_#{@services_data[id][:raw].visits_number}"
unassigned << Models::Solution::Step.new(service_in_vrp,
unassigned << Models::Solution::Stop.new(service_in_vrp,
service_id: unassigned_id,
reason: @services_assignment[id][:unassigned_reasons].join(','))
}
Expand Down Expand Up @@ -460,15 +460,15 @@ def reorder_stops(vrp)
back_to_depot = route_data[:stops].last[:end] +
matrix(route_data, route_data[:stops].last[:point_id], route_data[:end_point_id])
periodic_route_time = back_to_depot - route_data[:stops].first[:start]
solver_route_time = (solution.routes.first.steps.last.info.begin_time -
solution.routes.first.steps.first.info.begin_time) # last activity is vehicle depot
solver_route_time = (solution.routes.first.stops.last.info.begin_time -
solution.routes.first.stops.first.info.begin_time) # last activity is vehicle depot

minimum_duration = @candidate_services_ids.flat_map{ |s| @services_data[s][:durations] }.min
original_indices = route_data[:stops].collect{ |s| @indices[s[:id]] }
next if periodic_route_time - solver_route_time < minimum_duration ||
solution.routes.first.steps.collect{ |act| @indices[act.service_id] }.compact == original_indices # we did not change our points order
solution.routes.first.stops.collect{ |act| @indices[act.service_id] }.compact == original_indices # we did not change our points order

route_data[:stops] = compute_route_from(route_data, solution.routes.first.steps)
route_data[:stops] = compute_route_from(route_data, solution.routes.first.stops)
}
}
end
Expand Down Expand Up @@ -1104,23 +1104,23 @@ def matrix(route_data, start, arrival, dimension = :time)
def get_stop(day, type, vehicle, data = {})
size_weeks = (@schedule_end.to_f / 7).ceil.to_s.size
week = Helper.string_padding(day / 7 + 1, size_weeks)
times = Models::Solution::Step::Info.new(begin_time: data[:arrival],
times = Models::Solution::Stop::Info.new(begin_time: data[:arrival],
day_week_num: "#{day % 7}_#{week}",
day_week: "#{OptimizerWrapper::WEEKDAYS[day % 7]}_#{week}")

case type
when 'start'
Models::Solution::Step.new(vehicle.start_point, info: times)
Models::Solution::Stop.new(vehicle.start_point, info: times)
when 'end'
Models::Solution::Step.new(vehicle.end_point, info: times)
Models::Solution::Stop.new(vehicle.end_point, info: times)
when 'service'
service = @services_data[data[:id]][:raw] if type == 'service'
loads = service.quantities.map{ |quantity|
Models::Solution::Load.new(quantity: Models::Quantity.new(unit: quantity.unit))
}
number_in_sequence =
@services_assignment[data[:id]][:assigned_indices][@services_assignment[data[:id]][:days].find_index(day)]
Models::Solution::Step.new(
Models::Solution::Stop.new(
service,
service_id: "#{data[:id]}_#{number_in_sequence}_#{service.visits_number}",
visit_index: number_in_sequence,
Expand Down Expand Up @@ -1156,14 +1156,14 @@ def get_route_data(route_data)
end

def get_activities(day, route_data, vehicle)
computed_steps = []
computed_stops = []
route_start, route_end, _final_travel_time, _final_travel_distance = get_route_data(route_data)

computed_steps << get_stop(day, 'start', vehicle, arrival: route_start) if route_data[:start_point_id]
computed_steps += route_data[:stops].map{ |stop| get_stop(day, 'service', vehicle, stop) }
computed_steps << get_stop(day, 'end', vehicle, arrival: route_end) if route_data[:end_point_id]
computed_stops << get_stop(day, 'start', vehicle, arrival: route_start) if route_data[:start_point_id]
computed_stops += route_data[:stops].map{ |stop| get_stop(day, 'service', vehicle, stop) }
computed_stops << get_stop(day, 'end', vehicle, arrival: route_end) if route_data[:end_point_id]

[computed_steps, route_start, route_end]
[computed_stops, route_start, route_end]
end

# At the end of algorithm, deduces which visit number is assigned
Expand Down Expand Up @@ -1203,14 +1203,14 @@ def prepare_output_and_collect_routes(vrp)
# in case two vehicles have same global_day_index :
v.timewindow.start % 86400 == route_data[:tw_start] && v.timewindow.end % 86400 == route_data[:tw_end]
}
computed_steps, _start_time, _end_time = get_activities(day, route_data, vrp_vehicle)
computed_stops, _start_time, _end_time = get_activities(day, route_data, vrp_vehicle)

vrp_routes << {
vehicle_id: vrp_vehicle.id,
mission_ids: computed_steps.collect{ |stop| stop[:service_id] }.compact
mission_ids: computed_stops.collect{ |stop| stop[:service_id] }.compact
}

solution_routes << Models::Solution::Route.new(steps: computed_steps,
solution_routes << Models::Solution::Route.new(stops: computed_stops,
vehicle: vrp_vehicle)
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/interpreters/compute_several_solutions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def self.find_best_heuristic(service_vrp)
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.steps.collect(&:service_id).compact
mission_ids = route.stops.collect(&:service_id).compact
next if mission_ids.empty?

Models::Route.create(vehicle: vrp.vehicles.find{ |v| v.id == route.vehicle_id }, mission_ids: mission_ids)
Expand Down
22 changes: 11 additions & 11 deletions lib/interpreters/split_clustering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def self.split_clusters(service_vrp, job = nil, &block)
next if solution.nil?

solution.routes.each{ |route|
route.steps.each do |stop|
route.stops.each do |stop|
next unless stop.service_id

stop.skills = stop.skills.to_a + ["cluster #{cluster_ref}"]
Expand Down Expand Up @@ -549,7 +549,7 @@ def self.remove_poor_routes(vrp, solution)
end

def self.remove_empty_routes(solution)
solution.routes.delete_if{ |route| route.steps.none?(&:service_id) }
solution.routes.delete_if{ |route| route.stops.none?(&:service_id) }
end

def self.remove_poorly_populated_routes(vrp, solution, limit)
Expand All @@ -560,15 +560,15 @@ def self.remove_poorly_populated_routes(vrp, solution, limit)
emptied_routes = false
solution.routes.delete_if{ |route|
vehicle = vrp.vehicles.find{ |current_vehicle| current_vehicle.id == route.vehicle_id }
loads = route.steps.last.loads
loads = route.stops.last.loads
load_flag = vehicle.capacities.empty? || vehicle.capacities.all?{ |capacity|
current_load = loads.find{ |unit_load| unit_load.quantity.unit.id == capacity.unit.id }
current_load.current / capacity.limit < limit if capacity.limit && current_load && capacity.limit > 0
}
vehicle_worktime = vehicle.duration ||
vehicle.timewindow&.end && (vehicle.timewindow.end - vehicle.timewindow.start)
route_duration = route.info.total_time ||
(route.steps.last.info.begin_time - route.steps.first.info.begin_time)
(route.stops.last.info.begin_time - route.stops.first.info.begin_time)
log "route #{route.vehicle.id} time: #{route_duration}/#{vehicle_worktime} percent: " \
"#{((route_duration / (vehicle_worktime || route_duration).to_f) * 100).to_i}%", level: :info

Expand All @@ -588,12 +588,12 @@ def self.remove_poorly_populated_routes(vrp, solution, limit)
result[key] -= route[key] if route[key]
}

number_of_services_in_the_route = route.steps.count(&:service_id)
number_of_services_in_the_route = route.stops.count(&:service_id)

log "route #{route.vehicle.id} is emptied: #{number_of_services_in_the_route} " \
'services are now unassigned.', level: :info

solution.unassigned += route.steps.select(&:service_id)
solution.unassigned += route.stops.select(&:service_id)
true
end
}
Expand Down Expand Up @@ -913,7 +913,7 @@ def self.split_hierarchical(service_vrp, nb_clusters, options = {})

def self.collect_cluster_data(vrp, nb_clusters)
# TODO: due to historical dev, this function is in two pieces but
# it is possbile to do the same task in one step. That is, instead of
# it is possbile to do the same task in one stop. That is, instead of
# collecting vehicles then eliminating to have nb_clusters vehicles,
# we can create one with nb_clusters items directly.

Expand Down Expand Up @@ -1044,12 +1044,12 @@ def collect_data_items_metrics(vrp, cumulated_metrics, options)
decimal = if !vrp.matrices.empty? && !vrp.matrices[0][:distance]&.empty? # If there is a matrix, zip_dataitems will be called so no need to group by lat/lon aggresively
{
digits: 4, # 3: 111.1 meters, 4: 11.11m, 5: 1.111m accuracy
steps: 5 # digits.steps 4.0: 11.11m, 4.1: 5.6m, 4.2: 3.7m, 4.3: 2.8m, 4.4: 2.2m, 4.5: 1.9m, 4.6: 1.6m, 4.7: 1.4m, 4.8: 1.2m, 4.9=5.0: 1.111m
stops: 5 # digits.stops 4.0: 11.11m, 4.1: 5.6m, 4.2: 3.7m, 4.3: 2.8m, 4.4: 2.2m, 4.5: 1.9m, 4.6: 1.6m, 4.7: 1.4m, 4.8: 1.2m, 4.9=5.0: 1.111m
}
else
{
digits: 3, # 3: 111.1 meters, 4: 11.11m, 5: 1.111m accuracy
steps: 8 # digits.steps 3.0: 111.1m, 3.1: 56m, 3.2: 37m, 3.3: 28m, 3.4: 22m, 3.5: 19m, 3.6: 16m, 3.7: 14m, 3.8: 12m, 3.9=4.0: 11.11m
stops: 8 # digits.stops 3.0: 111.1m, 3.1: 56m, 3.2: 37m, 3.3: 28m, 3.4: 22m, 3.5: 19m, 3.6: 16m, 3.7: 14m, 3.8: 12m, 3.9=4.0: 11.11m
}
end

Expand Down Expand Up @@ -1110,8 +1110,8 @@ def collect_data_items_metrics(vrp, cumulated_metrics, options)
end

{
lat: location.lat.round_with_steps(decimal[:digits], decimal[:steps]),
lon: location.lon.round_with_steps(decimal[:digits], decimal[:steps]),
lat: location.lat.round_with_steps(decimal[:digits], decimal[:stops]),
lon: location.lon.round_with_steps(decimal[:digits], decimal[:stops]),
v_id: s[:sticky_vehicle_ids].to_a |
[vrp.routes.find{ |r| r.mission_ids.include? s.id }&.vehicle_id].compact, # split respects initial routes
skills: s.skills.to_a.dup,
Expand Down
Loading

0 comments on commit 3c299ee

Please sign in to comment.