@@ -24,7 +24,7 @@ def solver_constraints
2424 :assert_no_ride_constraint ,
2525 :assert_no_service_duration_modifiers ,
2626 :assert_vehicles_no_alternative_skills ,
27- :assert_vehicles_no_force_start ,
27+ :assert_vehicles_no_force_start , # Use shift_preference instead
2828 :assert_vehicles_no_initial_load ,
2929 :assert_vehicles_no_late_multiplier ,
3030 :assert_vehicles_no_overload_multiplier ,
@@ -173,7 +173,7 @@ def read_depot_end(vrp, vehicle)
173173 end
174174
175175 def read_reload_depot_trip ( vrp , vehicle , reload_depot_index )
176- reload_depot = @reload_depots [ reload_depot_index ]
176+ reload_depot = @reload_depots [ @depots . size - reload_depot_index ]
177177 return nil if reload_depot . nil?
178178
179179 route_data = compute_route_data ( vrp , vehicle , reload_depot . point )
@@ -224,6 +224,11 @@ def pyvrp_problem(vrp)
224224
225225 # Skills can be considered as capacities
226226 @skills_index_hash = { }
227+
228+ # to keep the client and depot indices consistent, the depots should be built before the clients and the matrices
229+ @point_hash = vrp . points . index_by ( &:id )
230+ depots = build_depots ( vrp )
231+
227232 vrp . vehicles . map ( &:skills ) . flatten . uniq . each_with_index { |skill , index | @skills_index_hash [ skill ] = index }
228233 used_matrices = vrp . vehicles . map ( &:matrix_id ) . uniq
229234 matrices = used_matrices . map { |id | vrp . matrices . find { |m | m . id == id } }
@@ -233,9 +238,6 @@ def pyvrp_problem(vrp)
233238
234239 distance_matrices = duration_matrices if distance_matrices . empty?
235240
236- # to keep the client indices consistent, the depots should be built before the clients
237- depots = build_depots ( vrp )
238-
239241 @reload_depot_index_hash = { }
240242 vrp . reload_depots . each_with_index { |depot , index | @reload_depot_index_hash [ depot . id ] = depots . size + index }
241243 clients , groups = build_clients_and_groups ( vrp )
@@ -253,11 +255,7 @@ def pyvrp_problem(vrp)
253255 end
254256
255257 def expand_matrices ( vrp , distance_matrices , duration_matrices )
256- depot_points =
257- vrp . vehicles . flat_map { |veh |
258- [ veh . start_point , veh . end_point ]
259- } . uniq
260- additive_setups = Array . new ( depot_points . size , 0 )
258+ additive_setups = Array . new ( @depots . size , 0 )
261259
262260 reload_depot_points =
263261 vrp . reload_depots . map ( &:point )
@@ -272,7 +270,7 @@ def expand_matrices(vrp, distance_matrices, duration_matrices)
272270 points
273271 }
274272
275- all_points = ( depot_points + reload_depot_points + client_points )
273+ all_points = ( @depots + reload_depot_points + client_points )
276274
277275 distance_matrices . map! do |matrix |
278276 matrix =
@@ -304,10 +302,6 @@ def distance(matrix, point1, point2)
304302
305303 def build_vehicles ( vrp )
306304 used_matrices = vrp . vehicles . map ( &:matrix_id ) . uniq
307- @depot_index_hash =
308- vrp . vehicles . flat_map { |veh |
309- [ veh . start_point , veh . end_point ]
310- } . uniq . each_with_index . map { |pt , idx | [ pt &.id , idx ] } . to_h
311305 all_units = vrp . units . index_by ( &:id )
312306
313307 vrp . vehicles . map { |veh |
@@ -325,8 +319,8 @@ def build_vehicles(vrp)
325319 {
326320 num_available : 1 ,
327321 capacity : capacity_hash . values + capacity_skills ,
328- start_depot : @depot_index_hash [ veh . start_point & .id ] ,
329- end_depot : @depot_index_hash [ veh . end_point & .id ] ,
322+ start_depot : @vehicle_start_point_index_hash [ veh . id ] ,
323+ end_depot : @vehicle_end_point_index_hash [ veh . id ] ,
330324 fixed_cost : veh . cost_fixed . to_i ,
331325 tw_early : veh . timewindow &.start || 0 ,
332326 tw_late : veh . timewindow &.end || MAX_INT64 ,
@@ -336,7 +330,7 @@ def build_vehicles(vrp)
336330 unit_duration_cost : veh . cost_time_multiplier . to_i ,
337331 profile : used_matrices . index ( veh . matrix_id ) ,
338332 start_late : nil ,
339- reload_depots : veh . reload_depots . map { |depot | @depot_hash [ depot . id ] } ,
333+ reload_depots : veh . reload_depots . map { |depot | @reload_depot_hash [ depot . id ] } ,
340334 max_reloads : veh . maximum_reloads || 0 ,
341335 name : veh . id . to_s
342336 }
@@ -414,26 +408,106 @@ def build_clients_and_groups(vrp)
414408 [ client_list , groups ]
415409 end
416410
411+ def add_depot_point ( point , index_hash , criteria = nil )
412+ return if point . nil?
413+
414+ return index_hash [ point . id ] if index_hash . key? ( point . id ) && index_hash [ point . id ] . is_a? ( Integer )
415+
416+ return index_hash [ point . id ] [ criteria ] if index_hash [ point . id ] . is_a? ( Hash ) && index_hash [ point . id ] . key? ( criteria )
417+
418+ @depots << point
419+ if criteria
420+ index_hash [ point . id ] ||= { }
421+ index_hash [ point . id ] [ criteria ] = index_hash [ point . id ] . size
422+ else
423+ index_hash [ point . id ] = index_hash . size
424+ end
425+ end
426+
417427 def build_depots ( vrp )
418- depot_points = vrp . vehicles . flat_map { |vehicle | [ vehicle . start_point , vehicle . end_point ] } . uniq
419- @reload_depots = Array . new ( depot_points . size , nil )
420- @depot_hash = { }
421- @depot_vehicle_hash = { }
422- depots =
423- depot_points . map do |point |
428+ @depots = [ ]
429+ @vehicle_start_point_index_hash = { }
430+ @vehicle_end_point_index_hash = { }
431+ @depot_points_standard_index_hash = { }
432+ @depot_points_force_start_by_timewindow_start_index_hash = { }
433+ @depot_points_force_end_by_timewindow_end_index_hash = { }
434+ vrp . vehicles . group_by ( &:shift_preference ) . each do |shift_preference , vehicles |
435+ vehicles . group_by ( &:timewindow ) . each do |timewindow , sub_vehicles |
436+ case shift_preference
437+ when :force_start
438+ sub_vehicles . each do |vehicle |
439+ @vehicle_start_point_index_hash [ vehicle . id ] =
440+ add_depot_point (
441+ vehicle . start_point ,
442+ @depot_points_force_start_by_timewindow_start_index_hash ,
443+ timewindow . start
444+ )
445+ @vehicle_end_point_index_hash [ vehicle . id ] =
446+ add_depot_point ( vehicle . end_point , @depot_points_standard_index_hash )
447+ end
448+ when :force_end
449+ sub_vehicles . each do |vehicle |
450+ @vehicle_start_point_index_hash [ vehicle . id ] =
451+ add_depot_point ( vehicle . start_point , @depot_points_standard_index_hash )
452+ @vehicle_end_point_index_hash [ vehicle . id ] =
453+ add_depot_point (
454+ vehicle . end_point ,
455+ @depot_points_force_end_by_timewindow_end_index_hash ,
456+ timewindow . end
457+ )
458+ end
459+ when :minimize_span
460+ sub_vehicles . each do |vehicle |
461+ @vehicle_start_point_index_hash [ vehicle . id ] =
462+ add_depot_point ( vehicle . start_point , @depot_points_standard_index_hash )
463+ @vehicle_end_point_index_hash [ vehicle . id ] =
464+ add_depot_point ( vehicle . end_point , @depot_points_standard_index_hash )
465+ end
466+ end
467+ end
468+ end
469+ depots = Array . new ( @depots . size , nil )
470+ @depot_points_standard_index_hash . map { |point_id , index |
471+ depots [ index ] =
424472 {
425- x : point &.location &.lon || 0 ,
426- y : point &.location &.lat || 0 ,
473+ x : @point_hash [ point_id ] &.location &.lon || 0 ,
474+ y : @point_hash [ point_id ] &.location &.lat || 0 ,
427475 tw_early : 0 ,
428476 tw_late : MAX_INT64 ,
429- name : point &. id &. to_s || '_null_store'
477+ name : " #{ point_id } _standard" || '_null_store'
430478 }
431- end
479+ }
480+ @depot_points_force_start_by_timewindow_start_index_hash . each { |point_id , ( timewindow_start , point_indices ) |
481+ point_indices . map { |point_index |
482+ depots [ point_index ] =
483+ {
484+ x : @point_hash [ point_id ] &.location &.lon || 0 ,
485+ y : @point_hash [ point_id ] &.location &.lat || 0 ,
486+ tw_early : timewindow_start || 0 ,
487+ tw_late : timewindow_start ,
488+ name : "#{ point_id } _#{ timewindow_start } _force_start" || '_null_store'
489+ }
490+ }
491+ }
492+ @depot_points_force_end_by_timewindow_end_index_hash . keys . flat_map { |point_id , ( timewindow_end , point_indices ) |
493+ point_indices . map { |point_index |
494+ depots [ point_index ] = {
495+ x : @point_hash [ point_id ] &.location &.lon || 0 ,
496+ y : @point_hash [ point_id ] &.location &.lat || 0 ,
497+ tw_early : timewindow_end || 0 ,
498+ tw_late : timewindow_end || MAX_INT64 ,
499+ name : "#{ point_id } _#{ timewindow_end } _force_end" || '_null_store'
500+ }
501+ }
502+ }
503+
504+ @reload_depots = [ ]
505+ @reload_depot_hash = { }
432506 vrp . reload_depots . each do |depot |
433- next if @depot_hash . key? ( depot . id )
507+ next if @reload_depot_hash . key? ( depot . id )
434508
435509 @reload_depots << depot
436- @depot_hash [ depot . id ] = depot_points . size
510+ @reload_depot_hash [ depot . id ] = @depots . size
437511 depots <<
438512 {
439513 x : depot . point &.location &.lon || 0 ,
@@ -465,18 +539,18 @@ def build_routes(vrp)
465539 def build_trips ( vrp , route , vehicle_type )
466540 trips = [ ]
467541 vehicle = vrp . vehicles [ vehicle_type ]
468- end_depot = @depot_index_hash [ vehicle . end_point & .id ]
542+ end_depot = @vehicle_end_point_index_hash [ vehicle . id ]
469543 current_trip = {
470544 visits : [ ] ,
471545 vehicle_type : vehicle_type ,
472- start_depot : @depot_index_hash [ vehicle . start_point & .id ] ,
546+ start_depot : @vehicle_start_point_index_hash [ vehicle . id ] ,
473547 end_depot : end_depot
474548 }
475549 route . missions . each do |mission |
476550 if mission . is_a? ( Models ::Service )
477551 current_trip [ :visits ] << @service_index_map . find_index { |service | service && service . id == mission . id }
478552 elsif mission . is_a? ( Models ::ReloadDepot )
479- reload_depot = @depot_hash [ mission . id ]
553+ reload_depot = @reload_depot_hash [ mission . id ]
480554 current_trip [ :end_depot ] = reload_depot
481555 trips << current_trip
482556 current_trip = {
0 commit comments