Skip to content

Commit

Permalink
Merge pull request #254 from senhalil/dev
Browse files Browse the repository at this point in the history
Improve base.rb & update active_hash
  • Loading branch information
fab-girard authored Aug 10, 2021
2 parents 532f82b + c6aa5c2 commit 721e32b
Show file tree
Hide file tree
Showing 41 changed files with 270 additions and 310 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ gem 'grape-swagger-entity'
gem 'grape_logging'

gem 'actionpack'
gem 'active_hash', github: 'Mapotempo/active_hash', branch: 'mapo'
gem 'active_hash', github: 'mapotempo/active_hash', branch: 'dev' # waiting for the following PRs to get merged and "released!" https://github.com/zilkey/active_hash/pull/231 and https://github.com/zilkey/active_hash/pull/233
gem 'activemodel'

gem 'charlock_holmes'
Expand Down
16 changes: 8 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
GIT
remote: https://github.com/Mapotempo/active_hash.git
revision: e2b18fedb32aec0cc03f0c747dbc682cbb8fc488
branch: mapo
specs:
active_hash (3.1.0)
activesupport (>= 5.0.0)

GIT
remote: https://github.com/Mapotempo/balanced_vrp_clustering.git
revision: 4b2b48732604731e1759788eab85f7ac6268835d
Expand All @@ -16,6 +8,14 @@ GIT
color-generator
geojson2image

GIT
remote: https://github.com/mapotempo/active_hash.git
revision: d04b246227675806f040be09e7bf0dfaa8ffc590
branch: dev
specs:
active_hash (3.1.0)
activesupport (>= 5.0.0)

GIT
remote: https://github.com/senhalil/rack.git
revision: a23188bb32972dde155977655f57dea035eee6ea
Expand Down
2 changes: 1 addition & 1 deletion lib/filters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def self.merge_timewindows(vrp)
# latest_day_index = day_indices.include?(nil) ? nil : day_indices.max
earliest_start = starts.include?(nil) ? nil : starts.min
latest_end = ends.include?(nil) ? nil : ends.max
new_timewindows << Models::Timewindow.new(start: earliest_start, end: latest_end, day_index: earliest_day_index)
new_timewindows << Models::Timewindow.create(start: earliest_start, end: latest_end, day_index: earliest_day_index)
}
service.activity.timewindows = new_timewindows
}
Expand Down
22 changes: 10 additions & 12 deletions lib/heuristics/dichotomious_approach.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ def self.dichotomious_heuristic(service_vrp, job = nil, &block)
if sub_service_vrp[:vrp].resolution_minimum_duration
sub_service_vrp[:vrp].resolution_minimum_duration *= sub_service_vrp[:vrp].services.size / service_vrp[:vrp].services.size.to_f * 2
end
matrix_indices = sub_service_vrp[:vrp].points.map{ |point|
service_vrp[:vrp].points.find{ |r_point| point.id == r_point.id }.matrix_index
}
SplitClustering.update_matrix_index(sub_service_vrp[:vrp])
SplitClustering.update_matrix(service_vrp[:vrp].matrices, sub_service_vrp[:vrp], matrix_indices)
result = OptimizerWrapper.define_process(sub_service_vrp, job, &block)
if index.zero? && result
transfer_unused_vehicles(result, sub_service_vrps)
matrix_indices = sub_service_vrps[1][:vrp].points.map{ |point|
service_vrp[:vrp].points.find{ |r_point| point.id == r_point.id }.matrix_index
}
SplitClustering.update_matrix_index(sub_service_vrps[1][:vrp])
SplitClustering.update_matrix(service_vrp[:vrp].matrices, sub_service_vrps[1][:vrp], matrix_indices)
end

transfer_unused_vehicles(result, sub_service_vrps) if index.zero? && result

result
}
service_vrp[:vrp].resolution_split_number = sub_service_vrps[1][:vrp].resolution_split_number
Expand Down Expand Up @@ -211,10 +211,8 @@ def self.build_initial_routes(results)
mission_ids = route[:activities].map{ |activity| activity[:service_id] }.compact
next if mission_ids.empty?

Models::Route.new(
vehicle: {
id: route[:vehicle_id]
},
Models::Route.create(
vehicle_id: route[:vehicle_id],
mission_ids: mission_ids
)
}
Expand Down
4 changes: 2 additions & 2 deletions lib/heuristics/periodic_heuristic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,7 @@ def prepare_output_and_collect_routes(vrp)
computed_activities, start_time, end_time = get_activities(day, route_data, vrp, vrp_vehicle)

routes << {
vehicle: { id: vrp_vehicle.id },
vehicle_id: vrp_vehicle.id,
mission_ids: computed_activities.collect{ |stop| stop[:service_id] }.compact
}

Expand All @@ -1184,7 +1184,7 @@ def prepare_output_and_collect_routes(vrp)
unassigned = collect_unassigned
vrp[:preprocessing_heuristic_result] = {
cost: @cost,
cost_details: Models::CostDetails.new({}), # TODO: fulfill with solution costs
cost_details: Models::CostDetails.create({}), # TODO: fulfill with solution costs
solvers: ['heuristic'],
iterations: 0,
routes: solution,
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 @@ -214,7 +214,7 @@ def self.find_best_heuristic(service_vrp)
}.compact
next if mission_ids.empty?

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

Expand Down
6 changes: 3 additions & 3 deletions lib/interpreters/multi_modal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def generate_subproblems(patterns)
sub_vrp.vehicles += (1..duplicate_vehicles).collect{ |index|
if vehicle_skills && !vehicle_skills.empty?
vehicle_skills.collect{ |alternative|
Models::Vehicle.new(
Models::Vehicle.create(
id: "subtour_#{alternative.join('-')}_#{transmodal_id}_#{index}",
router_mode: sub_tour.router_mode,
router_dimension: sub_tour.router_dimension,
Expand All @@ -158,7 +158,7 @@ def generate_subproblems(patterns)
)
}
else
Models::Vehicle.new(
Models::Vehicle.create(
id: "subtour_#{transmodal_id}_#{index}",
router_mode: sub_tour.router_mode,
router_dimension: sub_tour.router_dimension,
Expand Down Expand Up @@ -227,7 +227,7 @@ def override_original_vrp(subresults)
subresult[:routes].collect{ |route|
next unless route[:activities].size > 2

service = Models::Service.new(
service = Models::Service.create(
id: route[:vehicle_id],
activity: {
point: @original_vrp.points.find{ |point| point.id == route[:activities].first[:point_id] },
Expand Down
104 changes: 61 additions & 43 deletions lib/interpreters/periodic_visits.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def expand(vrp, job, &block)
return vrp unless vrp.schedule?

vehicles_linking_relations = save_vehicle_linking_relations(vrp)
vrp.relations = generate_relations(vrp)
vrp.rests = []
vrp.vehicles = generate_vehicles(vrp).sort{ |a, b|
(a.global_day_index && b.global_day_index && a.global_day_index != b.global_day_index) ? a.global_day_index <=> b.global_day_index : a.id <=> b.id
Expand All @@ -53,6 +52,7 @@ def expand(vrp, job, &block)
end

vrp.services = generate_services(vrp)
vrp.relations = generate_relations(vrp)

@periods.uniq!
generate_relations_on_periodic_vehicles(vrp, vehicles_linking_relations)
Expand All @@ -78,8 +78,8 @@ def generate_timewindows(timewindows_set)

if first_day
(first_day..@schedule_end).step(timewindow.day_index ? 7 : 1).collect{ |day_index|
Models::Timewindow.new(start: timewindow.start + day_index * 86400,
end: (timewindow.end || 86400) + day_index * 86400)
Models::Timewindow.create(start: timewindow.start + day_index * 86400,
end: (timewindow.end || 86400) + day_index * 86400)
}
end
else
Expand All @@ -89,21 +89,22 @@ def generate_timewindows(timewindows_set)
end

def generate_relations(vrp)
vrp.relations.collect{ |relation|
related_missions = relation.linked_services
vrp.relations.flat_map{ |relation|
unless relation.linked_ids.uniq{ |s_id| @expanded_services[s_id].size }.size <= 1
raise "Cannot expand relations of #{relation.linked_ids} because they have different visits_number"
end

visits_number = related_missions.first&.visits_number
next unless related_missions.any? && related_missions.all?{ |mission| mission.visits_number == visits_number }
# keep the original relation if it is another type of relation or if it doesn't belong to an unexpanded service.
next relation if relation.linked_services.empty? || @expanded_services[relation.linked_ids.first].empty?

(1..visits_number).collect{ |relation_index|
linked_ids = related_missions.collect{ |mission|
"#{mission.id}_#{relation_index}_#{mission.visits_number}"
}
new_relation = Models::Relation.create(type: relation.type, linked_ids: linked_ids,
lapse: relation.lapse, periodicity: relation.periodicity)
new_relation
Array.new(@expanded_services[relation.linked_ids.first].size){ |visit_index|
linked_ids = relation.linked_ids.collect{ |s_id| @expanded_services[s_id][visit_index].id }

Models::Relation.create(
type: relation.type, linked_ids: linked_ids, lapse: relation.lapse, periodicity: relation.periodicity
)
}
}.compact.flatten
}
end

def generate_relations_between_visits(vrp, mission)
Expand All @@ -113,34 +114,43 @@ def generate_relations_between_visits(vrp, mission)
if mission.minimum_lapse && mission.maximum_lapse
(2..mission.visits_number).each{ |index|
current_lapse = (index - 1) * mission.minimum_lapse.to_i
vrp.relations << Models::Relation.new(type: :minimum_day_lapse,
linked_ids: ["#{mission.id}_1_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse)
vrp.relations << Models::Relation.create(
type: :minimum_day_lapse,
linked_ids: ["#{mission.id}_1_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse
)
}
(2..mission.visits_number).each{ |index|
current_lapse = (index - 1) * mission.maximum_lapse.to_i
vrp.relations << Models::Relation.new(type: :maximum_day_lapse,
linked_ids: ["#{mission.id}_1_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse)
vrp.relations << Models::Relation.create(
type: :maximum_day_lapse,
linked_ids: ["#{mission.id}_1_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse
)
}
elsif mission.minimum_lapse
(2..mission.visits_number).each{ |index|
current_lapse = mission.minimum_lapse.to_i
vrp.relations << Models::Relation.new(type: :minimum_day_lapse,
linked_ids: ["#{mission.id}_#{index - 1}_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse)
vrp.relations << Models::Relation.create(
type: :minimum_day_lapse,
linked_ids: ["#{mission.id}_#{index - 1}_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse
)
}
elsif mission.maximum_lapse
(2..mission.visits_number).each{ |index|
current_lapse = mission.maximum_lapse.to_i
vrp.relations << Models::Relation.new(type: :maximum_day_lapse,
linked_ids: ["#{mission.id}_#{index - 1}_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse)
vrp.relations << Models::Relation.create(
type: :maximum_day_lapse,
linked_ids: ["#{mission.id}_#{index - 1}_#{mission.visits_number}", "#{mission.id}_#{index}_#{mission.visits_number}"],
lapse: current_lapse
)
}
end
end

def generate_services(vrp)
@expanded_services = Hash.new{ |h, k| h[k] = [] }
vrp.services.collect{ |service|
# transform service data into periodic data
(service.activity ? [service.activity] : service.activities).each{ |activity|
Expand All @@ -149,10 +159,9 @@ def generate_services(vrp)

# generate one service per visit
# TODO : create visit in model
generate_relations_between_visits(vrp, service)
@periods << service.visits_number

(0..service.visits_number - 1).collect{ |visit_index|
visits = (0..service.visits_number - 1).collect{ |visit_index|
next if service.unavailable_visit_indices.include?(visit_index)

new_service = duplicate_safe(
Expand All @@ -164,8 +173,14 @@ def generate_services(vrp)
)
new_service.skills += ["#{visit_index + 1}_f_#{service.visits_number}"] if !service.minimum_lapse && !service.maximum_lapse && service.visits_number > 1

@expanded_services[service.id] << new_service

new_service
}.compact

generate_relations_between_visits(vrp, service)

visits
}.flatten
end

Expand Down Expand Up @@ -201,7 +216,7 @@ def generate_vehicles(vrp)
else
timewindows.select{ |timewindow| timewindow.day_index.nil? || timewindow.day_index == vehicle_day_index % 7 }.collect{ |associated_timewindow|
new_vehicle = build_vehicle(vrp, vehicle, vehicle_day_index, rests_durations)
new_vehicle.timewindow = Models::Timewindow.new(start: associated_timewindow.start || 0, end: associated_timewindow.end || 86400)
new_vehicle.timewindow = Models::Timewindow.create(start: associated_timewindow.start || 0, end: associated_timewindow.end || 86400)
if @have_day_index
new_vehicle.timewindow.start += vehicle_day_index * 86400
new_vehicle.timewindow.end += vehicle_day_index * 86400
Expand All @@ -212,7 +227,7 @@ def generate_vehicles(vrp)
}.compact

if vehicle.overall_duration
new_relation = Models::Relation.new(
new_relation = Models::Relation.create(
type: :vehicle_group_duration,
linked_vehicle_ids: @equivalent_vehicles[vehicle.original_id],
lapse: vehicle.overall_duration + rests_durations[index]
Expand Down Expand Up @@ -405,10 +420,11 @@ def compute_possible_days(vrp)
end

def save_vehicle_linking_relations(vrp)
vrp.relations.select{ |r|
vehicle_linking_relations, vrp.relations = vrp.relations.partition{ |r|
[:vehicle_group_duration, :vehicle_group_duration_on_weeks, :vehicle_group_duration_on_months,
:vehicle_trips].include?(r.type)
}
vehicle_linking_relations
end

def cut_linking_vehicle_relation_by_period(relation, periods, relation_type)
Expand All @@ -421,7 +437,7 @@ def cut_linking_vehicle_relation_by_period(relation, periods, relation_type)
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(
additional_relations << Models::Relation.create(
linked_vehicle_ids: relation_vehicles,
lapse: relation.lapse,
type: relation_type
Expand All @@ -447,9 +463,11 @@ 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
Models::Relation.new(
type: :vehicle_group_duration, lapse: relation.lapse,
linked_vehicle_ids: relation[:linked_vehicle_ids].flat_map{ |v| @equivalent_vehicles[v] })
Models::Relation.create(
type: :vehicle_group_duration,
linked_vehicle_ids: relation[:linked_vehicle_ids].flat_map{ |v| @equivalent_vehicles[v] },
lapse: relation.lapse
)
when :vehicle_group_duration_on_weeks
schedule_week_indices = collect_weeks_in_schedule
cut_linking_vehicle_relation_by_period(relation, schedule_week_indices, :vehicle_group_duration)
Expand All @@ -466,21 +484,21 @@ def generate_relations_on_periodic_vehicles(vrp, vehicle_linking_relations)
private

def get_original_values(original, options)
# Except the following keys (which do not have a non-id version) skip the id version to crete a shallow copy
fields_without_a_non_id_method = %i[original_id matrix_id value_matrix_id].freeze
[original.attributes.keys + options.keys].flatten.each_with_object({}) { |key, data|
next if [:sticky_vehicle_ids, :quantity_ids,
:start_point_id, :end_point_id, :capacity_ids, :sequence_timewindow_ids, :timewindow_id].include?(key)
next if (key[-3..-1] == '_id' || key[-4..-1] == '_ids') && fields_without_a_non_id_method.exclude?(key)

# if a key is supplied in the options manually as nil, this means removing the key
next if options.key?(key) && options[key].nil?

data[key] = options[key] || original[key]
}
end

def duplicate_safe(original, options = {})
# TODO : replace by implementing initialize_copy function for shallow copy + create model for visits
if original.is_a?(Models::Service)
Models::Service.new(get_original_values(original, options))
elsif original.is_a?(Models::Vehicle)
Models::Vehicle.new(get_original_values(original, options))
end
original.class.create(get_original_values(original, options))
end
end
end
2 changes: 1 addition & 1 deletion lib/interpreters/split_clustering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ def self.duplicate_vehicle(vehicle, timewindow, schedule)
available_days.collect{ |day|
next unless (schedule[:start]..schedule[:end]).any?{ |day_index| day_index % 7 == day }

tw = timewindow ? Marshal.load(Marshal.dump(timewindow)) : Models::Timewindow.new({})
tw = timewindow ? Marshal.load(Marshal.dump(timewindow)) : Models::Timewindow.create({})
tw.day_index = day
provide_work_day_skill(vehicle, day)
new_vehicle = Marshal.load(Marshal.dump(vehicle))
Expand Down
Loading

0 comments on commit 721e32b

Please sign in to comment.