Skip to content

Commit

Permalink
Multi-tour with schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsecadeline committed Jun 16, 2021
1 parent 439f46b commit f6f6680
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 62 deletions.
108 changes: 49 additions & 59 deletions lib/interpreters/periodic_visits.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ def initialize(vrp)
def expand(vrp, job, &block)
return vrp unless vrp.scheduling?

vehicles_linked_by_duration = save_relations(vrp, :vehicle_group_duration).concat(
save_relations(vrp, :vehicle_group_duration_on_weeks)
).concat(
save_relations(vrp, :vehicle_group_duration_on_months)
)
vehicles_linking_relations = save_vehicle_linking_relations(vrp)
vrp.relations = generate_relations(vrp)
vrp.rests = []
vrp.vehicles = generate_vehicles(vrp).sort{ |a, b|
Expand All @@ -61,8 +57,7 @@ def expand(vrp, job, &block)
vrp.shipments = generate_shipments(vrp)

@periods.uniq!
vehicles_linked_by_duration = get_all_vehicles_in_relation(vehicles_linked_by_duration)
generate_relations_on_periodic_vehicles(vrp, vehicles_linked_by_duration)
generate_relations_on_periodic_vehicles(vrp, vehicles_linking_relations)

if vrp.preprocessing_first_solution_strategy.to_a.first != 'periodic' && vrp.services.any?{ |service| service.visits_number > 1 }
vrp.routes = generate_routes(vrp)
Expand Down Expand Up @@ -247,7 +242,6 @@ def generate_vehicles(vrp)
timewindows = [vehicle.timewindow || vehicle.sequence_timewindows].flatten
if timewindows.empty?
new_vehicle = build_vehicle(vrp, vehicle, vehicle_day_index, rests_durations)
@equivalent_vehicles[vehicle.original_id] << new_vehicle.id
new_vehicle
else
timewindows.select{ |timewindow| timewindow.day_index.nil? || timewindow.day_index == vehicle_day_index % 7 }.collect{ |associated_timewindow|
Expand Down Expand Up @@ -456,67 +450,63 @@ def compute_possible_days(vrp)
}
end

def save_relations(vrp, relation_type)
vrp.relations.select{ |r| r.type == relation_type }.collect{ |r|
{
type: r.type,
linked_vehicle_ids: r.linked_vehicle_ids,
lapse: r.lapse,
periodicity: r.periodicity
}
def save_vehicle_linking_relations(vrp)
vrp.relations.select{ |r|
[:vehicle_group_duration, :vehicle_group_duration_on_weeks, :vehicle_group_duration_on_months,
:vehicle_trips].include?(r.type)
}
end

def get_all_vehicles_in_relation(relations)
relations&.each{ |r|
next if r[:type] == :vehicle_group_duration
def cut_linking_vehicle_relation_by_period(relation, periods, relation_type)
additional_relations = []
vehicles_in_relation =
relation[:linked_vehicle_ids].flat_map{ |v| @equivalent_vehicles[v] }

new_list = []
r[:linked_vehicle_ids].each{ |v|
new_list.concat(@equivalent_vehicles[v])
}
r[:linked_vehicle_ids] = new_list
}
while periods.any?
days_in_period = periods.slice!(0, relation.periodicity).flatten
relation_vehicles = vehicles_in_relation.select{ |id| days_in_period.include?(id.split('_').last.to_i) }
next unless relation_vehicles.any?

additional_relations << Models::Relation.new(
linked_vehicle_ids: relation_vehicles,
lapse: relation.lapse,
type: relation_type
)
end

additional_relations
end

def generate_relations_on_periodic_vehicles(vrp, list)
new_relations = []
list.each{ |r|
case r[:type]
def collect_weeks_in_schedule
current_day = (@schedule_start + 1..@schedule_start + 7).find{ |d| d % 7 == 0 } # next monday
schedule_week_indices = [(@schedule_start..current_day - 1).to_a]
while current_day + 6 <= @schedule_end
schedule_week_indices << (current_day..current_day + 6).to_a
current_day += 7
end
schedule_week_indices << (current_day..@schedule_end).to_a unless current_day > @schedule_end

schedule_week_indices
end

def generate_relations_on_periodic_vehicles(vrp, vehicle_linking_relations)
vrp.relations.concat(vehicle_linking_relations.flat_map{ |relation|
case relation[:type]
when :vehicle_group_duration
new_relations << [r[:linked_vehicle_ids], r[:lapse]]
Models::Relation.new(
type: :vehicle_group_duration, lapse: relation.lapse,
linked_vehicle_ids: relation[:linked_vehicle_ids].flat_map{ |v| @equivalent_vehicles[v] })
when :vehicle_group_duration_on_weeks
current_sub_list = []
first_index = r[:linked_vehicle_ids].min.split('_').last.to_i
in_periodicity = first_index + 7 * (r[:periodicity] - 1)
max_index = (in_periodicity..in_periodicity + 7).find{ |index| index % 7 == 6 }
r[:linked_vehicle_ids].sort_by{ |v_id| v_id.split('_').last.to_i }.each{ |v_id|
this_index = v_id.split('_').last.to_i
if this_index <= max_index
current_sub_list << v_id
else
new_relations << [current_sub_list, r[:lapse]]
current_sub_list = [v_id]
first_index = this_index
in_periodicity = first_index + 7 * (r[:periodicity] - 1)
max_index = (in_periodicity..in_periodicity + 7).find{ |index| index % 7 == 6 }
end
}
new_relations << [current_sub_list, r[:lapse]]
schedule_week_indices = collect_weeks_in_schedule
cut_linking_vehicle_relation_by_period(relation, schedule_week_indices, :vehicle_group_duration)
when :vehicle_group_duration_on_months
(0..vrp.schedule_months_indices.size - 1).step(r[:periodicity]).collect{ |v| p vrp.schedule_months_indices.slice(v, v + r[:periodicity]).flatten }.each{ |month_indices|
new_relations << [r[:linked_vehicle_ids].select{ |id| month_indices.include?(id.split('_').last.to_i) }, r[:lapse]]
}
cut_linking_vehicle_relation_by_period(relation, vrp.schedule_months_indices, :vehicle_group_duration)
when :vehicle_trips
# we want want vehicle_trip relation per day :
all_days = (@schedule_start..@schedule_end).to_a
cut_linking_vehicle_relation_by_period(relation, all_days, :vehicle_trips)
end
}

new_relations.each{ |linked_vehicle_ids, lapse|
vrp.relations << Models::Relation.new(
type: :vehicle_group_duration,
linked_vehicle_ids: linked_vehicle_ids,
lapse: lapse
)
}
})
end

private
Expand Down
2 changes: 1 addition & 1 deletion models/concerns/validate_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def calculate_day_availabilities(vehicles, timewindow_arrays)
if timewindows.empty?
[]
else
days = timewindows.flat_map{ |tw| tw[:day_index] }
days = timewindows.flat_map{ |tw| tw[:day_index] || (0..6).to_a }
days.compact!
days.uniq
end
Expand Down
42 changes: 40 additions & 2 deletions test/lib/interpreters/interpreter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,7 @@ def test_overall_duration_several_vehicles
assert_equal 2, expanded_vrp.relations.size

problem[:relations].first[:type] = :vehicle_group_duration
expanded_vrp = periodic_expand(vrp)
expanded_vrp = periodic_expand(problem)
assert_equal 1, expanded_vrp.relations.size
assert_equal 4, expanded_vrp.relations.first[:linked_vehicle_ids].size

Expand All @@ -1418,7 +1418,7 @@ def test_overall_duration_several_vehicles
}
vrp = TestHelper.create(problem)
refute_empty vrp.schedule_months_indices
expanded_vrp = periodic.send(:expand, vrp, nil)
expanded_vrp = periodic_expand(problem)
assert_equal 2, expanded_vrp.relations.size
end

Expand Down Expand Up @@ -1484,6 +1484,9 @@ def test_expand_relations_of_one_month_and_one_day
expanded_vrp = periodic_expand(problem)
assert_equal 2, expanded_vrp.relations.size

problem[:configuration][:schedule] = {
range_date: { start: Date.new(2020, 1, 1), end: Date.new(2020, 2, 1) }
}
problem[:relations].first[:periodicity] = 2
expanded_vrp = periodic_expand(problem)
assert_equal 1, expanded_vrp.relations.size
Expand Down Expand Up @@ -1554,4 +1557,39 @@ def test_first_last_possible_days
assert_equal 2, expanded_vrp.services.first.first_possible_days[0]
assert_equal 2, expanded_vrp.services.first.last_possible_days[0]
end

def test_expand_multitrips_relations
# If two vehicles are in vehicle_trips relation,
# VRP is only valid if they available at same days
# In this case, vehicle_trip relation should be repeated
# every day those vehicles are available
vrp = VRP.lat_lon_two_vehicles
vrp[:configuration][:schedule] = { range_indices: { start: 0, end: 3 }}
vrp[:relations] = [TestHelper.vehicle_trips_relation(vrp)]
vrp[:vehicles].each{ |v|
v.delete(:sequence_timewindows)
v[:timewindow] = { start: 0, end: 10 }
}
expanded_vrp = periodic_expand(vrp)
assert_equal 8, expanded_vrp.vehicles.size
assert_equal 4, (expanded_vrp.relations.count{ |r| r[:type] == :vehicle_trips })

vrp[:vehicles].each{ |v|
v[:timewindow] = { start: 0, end: 10, day_index: 0 }
}
expanded_vrp = periodic_expand(vrp)
assert_equal 2, expanded_vrp.vehicles.size
assert_equal 1, (expanded_vrp.relations.count{ |r| r[:type] == :vehicle_trips })

vrp[:vehicles].each{ |v|
v.delete(:timewindow)
}
vrp[:vehicles][0][:sequence_timewindows] = [{ start: 0, end: 10, day_index: 0 },
{ start: 20, end: 30, day_index: 1 }]
vrp[:vehicles][1][:sequence_timewindows] = [{ start: 5, end: 15, day_index: 0 },
{ start: 17, end: 45, day_index: 1 }]
expanded_vrp = periodic_expand(vrp)
assert_equal 4, expanded_vrp.vehicles.size
assert_equal 2, (expanded_vrp.relations.count{ |r| r[:type] == :vehicle_trips })
end
end
28 changes: 28 additions & 0 deletions test/models/vrp_consistency_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,34 @@ def test_compatible_days_availabilities_with_vehicle_trips
end
end

def test_compatible_days_availabilities_with_vehicle_trips_with_sequence_timewindows
vrp = VRP.lat_lon_two_vehicles
vrp[:configuration][:schedule] = { range_indices: { start: 0, end: 3 }}
vrp[:relations] = [TestHelper.vehicle_trips_relation(vrp)]
vrp[:vehicles][0][:sequence_timewindows] = [{ start: 0, end: 10, day_index: 0 },
{ start: 20, end: 30, day_index: 1 }]
vrp[:vehicles][1][:sequence_timewindows] = [{ start: 5, end: 15, day_index: 0 },
{ start: 17, end: 45, day_index: 1 }]

check_consistency(vrp) # this should not raise

vrp[:vehicles][1][:sequence_timewindows] = [{ start: 5, end: 15, day_index: 0 }]
assert_raises OptimizerWrapper::UnsupportedProblemError do
check_consistency(vrp)
end

vrp[:vehicles][1][:sequence_timewindows] = [{ start: 5, end: 15 }]
assert_raises OptimizerWrapper::UnsupportedProblemError do
check_consistency(vrp)
end

vrp[:vehicles][1][:sequence_timewindows] = [{ start: 5, end: 15, day_index: 0 },
{ start: 17, end: 19, day_index: 1 }]
assert_raises OptimizerWrapper::DiscordantProblemError do
check_consistency(vrp)
end
end

def test_unavailable_days_with_vehicle_trips
vrp = VRP.lat_lon_scheduling_two_vehicles
vrp[:relations] = [TestHelper.vehicle_trips_relation(vrp)]
Expand Down

0 comments on commit f6f6680

Please sign in to comment.