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 ce3a554
Show file tree
Hide file tree
Showing 34 changed files with 678 additions and 677 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
36 changes: 18 additions & 18 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 @@ -584,7 +584,7 @@ def same_point_compatibility(service_id, day)
min_overall_lapse =
(@services_data[service_id][:raw].visits_number - 1) * @services_data[service_id][:heuristic_period]
if @relaxed_same_point_day
involved_days = (day..day + min_overall_lapse).step(@services_data[service_id][:heuristic_period]).to_a
involved_days = (day..day + min_overall_lapse).stop(@services_data[service_id][:heuristic_period]).to_a
common_days = involved_days & @points_assignment[same_point][:days]
expected_number =
if @services_data[service_id][:raw].visits_number > @points_assignment[same_point][:maximum_visits_number]
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
Loading

0 comments on commit ce3a554

Please sign in to comment.