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

Improve base.rb & update active_hash #254

Merged
merged 8 commits into from
Aug 10, 2021
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
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