Skip to content

Commit 67acec7

Browse files
braktarfrodrigo
authored andcommitted
Add light intermediate_solution
1 parent b85e9bf commit 67acec7

File tree

6 files changed

+51
-18
lines changed

6 files changed

+51
-18
lines changed

api/v01/entities/vrp_input.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ module VrpInput
125125
module VrpConfiguration
126126
extend Grape::API::Helpers
127127

128+
INTERMEDIATE_SOLUTION_VALUES = [false, true, 'light'].freeze
129+
128130
params :vrp_request_configuration do
129131
optional(:preprocessing, type: Hash, desc: 'Parameters independent from the search') do
130132
use :vrp_request_preprocessing
@@ -208,7 +210,7 @@ module VrpConfiguration
208210
'Specifies the geometry structures to be returned. Can be a subset of `[polylines encoded_polylines partitions]` or a boolean value to output all or no geometry. Polylines and encoded_polylines are not compatible together.' :
209211
'Specifies the geometry structures to be returned. Can be `partitions` to generate geometry structure for each partition or `true` for generating the geometry structure under each route.')
210212
optional(:geometry_polyline, type: Boolean, documentation: { hidden: true }, desc: '[DEPRECATED] Use geometry instead, with :polylines or :encoded_polylines')
211-
optional(:intermediate_solutions, type: Boolean, desc: 'Return intermediate solutions if available')
213+
optional(:intermediate_solutions, types: [String, Boolean], desc: 'Return intermediate solutions if available', coerce_with: ->(i) { INTERMEDIATE_SOLUTION_VALUES.include?(i)} )
212214
optional(:csv, type: Boolean, desc: 'The output is a CSV file if you do not specify api format')
213215
optional(:use_deprecated_csv_headers, type: Boolean, desc: 'Forces API to ignore provided language to return old CSV headers')
214216
optional(:allow_empty_result, type: Boolean, desc: 'Allow no solution from the solver used')

api/v01/entities/vrp_result.rb

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,24 @@ class VRPResultDetailedCosts < Grape::Entity
5555
end
5656

5757
class VrpResultSolutionRouteActivities < Grape::Entity
58-
expose :day_week_num, expose_nil: false, documentation: { type: String, desc: '' }
59-
expose :day_week, expose_nil: false, documentation: { type: String, desc: '' }
58+
expose :day_week_num, expose_nil: false, documentation: { type: String, desc: '' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
59+
expose :day_week, expose_nil: false, documentation: { type: String, desc: '' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
6060
expose :point_id, documentation: { type: String, desc: 'Linked spatial point' }
61-
expose :travel_distance, documentation: { type: Integer, desc: 'Travel distance from previous point (in m)' }
62-
expose :travel_time, documentation: { type: Integer, desc: 'Travel time from previous point (in s)' }
63-
expose :travel_value, documentation: { type: Integer, desc: 'Travel value from previous point' }
64-
expose :waiting_time, documentation: { type: Integer, desc: 'Idle time (in s)' }
65-
expose :setup_time, documentation: { type: Integer, desc: 'Effective setup duration (in s)'}
66-
expose :begin_time, documentation: { type: Integer, desc: 'Time visit starts' }
67-
expose :end_time, documentation: { type: Integer, desc: 'Time visit ends' }
68-
expose :departure_time, documentation: { type: Integer, desc: '' }
61+
expose :travel_distance, documentation: { type: Integer, desc: 'Travel distance from previous point (in m)' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
62+
expose :travel_time, documentation: { type: Integer, desc: 'Travel time from previous point (in s)' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
63+
expose :travel_value, documentation: { type: Integer, desc: 'Travel value from previous point' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
64+
expose :waiting_time, documentation: { type: Integer, desc: 'Idle time (in s)' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
65+
expose :setup_time, documentation: { type: Integer, desc: 'Effective setup duration (in s)'}, if: lambda { |instance, options| options[:detailed_solutions] == true }
66+
expose :begin_time, documentation: { type: Integer, desc: 'Time visit starts' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
67+
expose :end_time, documentation: { type: Integer, desc: 'Time visit ends' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
68+
expose :departure_time, documentation: { type: Integer, desc: '' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
6969
expose :service_id, expose_nil: false, documentation: { type: String, desc: 'Internal reference of the service' }
7070
expose :pickup_shipment_id, expose_nil: false, documentation: { type: String, desc: 'Internal reference of the shipment' }
7171
expose :delivery_shipment_id, expose_nil: false, documentation: { type: String, desc: 'Internal reference of the shipment' }
7272
expose :rest_id, expose_nil: false, documentation: { type: String, desc: 'Internal reference of the rest' }
73-
expose :detail, using: VrpResultSolutionRouteActivityDetails, documentation: { desc: '' }
73+
expose :detail, using: VrpResultSolutionRouteActivityDetails, documentation: { desc: '' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
7474
expose :type, documentation: { type: String, desc: 'depot, rest, service, pickup or delivery' }
75-
expose :current_distance, documentation: { type: Integer, desc: 'Travel distance from route start to current point (in m)' }
75+
expose :current_distance, documentation: { type: Integer, desc: 'Travel distance from route start to current point (in m)' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
7676
expose :alternative, documentation: { type: Integer, desc: 'When one service has alternative activities, index of the chosen one' }
7777
expose :visit_index, documentation: { type: Integer, desc: 'Index of the visit' }
7878
end
@@ -89,8 +89,8 @@ class VrpResultSolutionRoute < Grape::Entity
8989
expose :total_waiting_time, documentation: { type: Integer, desc: 'Sum of every idle time within the route (in s)' }
9090
expose :start_time, documentation: { type: Integer, desc: 'Give the actual start time of the current route if provided by the solve' }
9191
expose :end_time, documentation: { type: Integer, desc: 'Give the actual end time of the current route if provided by the solver' }
92-
expose :geometry, documentation: { type: String, desc: 'Contains the geometry of the route, if asked in first place' }
93-
expose :initial_loads, using: VrpResultDetailQuantities, documentation: { is_array: true, desc: 'Give the actual initial loads of the route' }
92+
expose :geometry, documentation: { type: String, desc: 'Contains the geometry of the route, if asked in first place' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
93+
expose :initial_loads, using: VrpResultDetailQuantities, documentation: { is_array: true, desc: 'Give the actual initial loads of the route' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
9494
expose :cost_details, using: VRPResultDetailedCosts, documentation: { desc: 'The impact of the current route within the solution cost' }
9595
end
9696

@@ -100,7 +100,7 @@ class VrpResultSolutionUnassigned < Grape::Entity
100100
expose :pickup_shipment_id, expose_nil: false, documentation: { type: String, desc: 'Internal reference of the shipment' }
101101
expose :delivery_shipment_id, expose_nil: false, documentation: { type: String, desc: 'Internal reference of the shipment' }
102102
expose :rest_id, expose_nil: false, documentation: { type: String, desc: 'Internal reference of the rest' }
103-
expose :detail, using: VrpResultSolutionRouteActivityDetails, documentation: { desc: '' }
103+
expose :detail, using: VrpResultSolutionRouteActivityDetails, documentation: { desc: '' }, if: lambda { |instance, options| options[:detailed_solutions] == true }
104104
expose :type, documentation: { type: String, desc: 'depot, rest, service, pickup or delivery' }
105105
expose :reason, documentation: { type: String, desc: 'Unassigned reason. Only available when activity was rejected within preprocessing fase or periodic first_solution_strategy.' }
106106
end

api/v01/vrp.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ class Vrp < APIBase
235235
graph: result_object[:graph]
236236
},
237237
geojsons: OutputHelper::Result.generate_geometry(result_object)
238-
}, with: VrpResult)
238+
}, with: VrpResult, detailed_solutions: job&.status&.to_sym == :completed || result_object.dig(:configuration, :intermediate_solutions))
239239
end
240240
# set nil to release memory because puma keeps the grape api endpoint object alive
241241
stored_result = nil # rubocop:disable Lint/UselessAssignment

models/solution/parsers/solution_parser.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def self.parse(solution, vrp, options = {})
3434
compute_result_total_dimensions_and_round_route_stats(solution)
3535
solution.configuration.geometry = vrp.configuration.restitution.geometry
3636
solution.configuration.schedule_start_date = vrp.configuration.schedule&.start_date
37+
solution.configuration.detailed_solutions = vrp.configuration.restitution.intermediate_solutions
3738

3839
log "solution - unassigned rate: #{solution.unassigned_stops.size} of (ser: #{vrp.visits} " \
3940
"(#{(solution.unassigned_stops.size.to_f / vrp.visits * 100).round(1)}%)"

models/solution/solution_configuration.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ class Configuration < Base
2424
field :geometry, default: false
2525
field :deprecated_headers, default: false
2626
field :schedule_start_date
27+
field :detailed_solutions, default: true
2728

2829
def +(other)
2930
Configuration.create(
3031
csv: csv || other.csv,
3132
geometry: (geometry + other.geometry).uniq,
3233
deprecated_headers: deprecated_headers || other.deprecated_headers,
33-
schedule_start_date: schedule_start_date
34+
schedule_start_date: schedule_start_date,
35+
detailed_solutions: detailed_solutions
3436
)
3537
end
3638
end

test/api/v01/with_solver_test.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,34 @@ def test_deleted_job
3939
delete_completed_job @job_id, api_key: 'ortools' if @job_id
4040
end
4141

42+
def test_light_intemediate_solution
43+
# using ORtools to make sure that optimization takes enough time to be cut before ending
44+
asynchronously start_worker: true do
45+
vrp = VRP.lat_lon.deep_merge!({ configuration: { restitution: { intermediate_solutions: 'light' }}})
46+
47+
@job_id = submit_vrp api_key: 'ortools', vrp: vrp
48+
response = wait_avancement_match @job_id, /run optimization, iterations [0-9]+/, api_key: 'ortools'
49+
refute_empty response['solutions'].to_a, "Solution is missing from the response body: #{response}"
50+
51+
response['solutions'].to_a.each{ |solution|
52+
solution['routes'].each{ |route|
53+
route['activities'].each{ |activity|
54+
refute_nil activity['point_id']
55+
refute_nil activity['type']
56+
refute_nil activity['service_id'] if activity['type'] == 'service'
57+
assert_nil activity['travel_time']
58+
assert_nil activity['travel_distance']
59+
assert_nil activity['detail']
60+
}
61+
}
62+
}
63+
64+
response = delete_job @job_id, api_key: 'ortools'
65+
refute_empty response['solutions'].to_a, "Solution is missing from the response body: #{response}"
66+
end
67+
delete_completed_job @job_id, api_key: 'ortools' if @job_id
68+
end
69+
4270
def test_using_two_solver
4371
asynchronously start_worker: true do
4472
problem = VRP.lat_lon

0 commit comments

Comments
 (0)