diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e658274..504cf625e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ - Return route day/date and visits' index in result [#196](https://github.com/Mapotempo/optimizer-api/pull/196) - Treat complex shipments (multi-pickup-single-delivery and single-pickup-multi-delivery) as multiple simple shipments internally to increase performance [#261](https://github.com/Mapotempo/optimizer-api/pull/261) - Prioritize the vehicles (and trips of `vehicle_trips` relation) via modifying the fixed costs so that first vehicles (and trips) are preferred over the latter ones [#266](https://github.com/Mapotempo/optimizer-api/pull/266) - - Document return codes [#224](https://github.com/Mapotempo/optimizer-api/pull/224) +- OR-Tools wrapper can use `initial` capacity value [#245](https://github.com/Mapotempo/optimizer-api/pull/245) ### Changed diff --git a/test/wrappers/ortools_test.rb b/test/wrappers/ortools_test.rb index 9bd9a965d..44e4eb512 100644 --- a/test/wrappers/ortools_test.rb +++ b/test/wrappers/ortools_test.rb @@ -5256,6 +5256,29 @@ def test_quantity_precision } end + def test_initial_quantity + problem = VRP.basic + problem[:services].first[:quantities] = [{ unit_id: 'kg', value: -1 }] + problem[:services].last[:quantities] = [{ unit_id: 'kg', value: -1 }] + problem[:vehicles].each{ |vehicle| + vehicle[:capacities] = [{ unit_id: 'kg', limit: 3, initial: 0 }] + } + + result = OptimizerWrapper.wrapper_vrp('demo', { services: { vrp: [:ortools] }}, TestHelper.create(problem), nil) + assert_equal 2, result[:unassigned].size, 'The result is expected to contain 2 unassigned' + + problem = VRP.basic + problem[:services].first[:quantities] = [{ unit_id: 'kg', value: -1 }] + problem[:services].last[:quantities] = [{ unit_id: 'kg', value: 1 }] + problem[:vehicles].each{ |vehicle| + vehicle[:capacities] = [{ unit_id: 'kg', limit: 3, initial: 0 }] + } + + result = OptimizerWrapper.wrapper_vrp('demo', { services: { vrp: [:ortools] }}, TestHelper.create(problem), nil) + assert result[:routes].first[:activities].index{ |act| act[:service_id] == problem[:services].last[:id] } < + result[:routes].first[:activities].index{ |act| act[:service_id] == problem[:services].first[:id] } + end + def test_simplify_vehicle_pause_without_timewindow_or_duration complete_vrp = VRP.pud complete_vrp[:rests] = [{ diff --git a/wrappers/ortools.rb b/wrappers/ortools.rb index c0d0e375e..9d492e3b1 100644 --- a/wrappers/ortools.rb +++ b/wrappers/ortools.rb @@ -36,7 +36,6 @@ def solver_constraints super + [ :assert_end_optimization, :assert_vehicles_objective, - :assert_vehicles_no_capacity_initial, :assert_vehicles_no_alternative_skills, :assert_zones_only_size_one_alternative, :assert_only_empty_or_fill_quantities, @@ -99,6 +98,31 @@ def solve(vrp, job, thread_proc = nil, &block) empty: false } } + total_quantities = vrp.units.map{ |unit| [unit.id, 0] }.to_h + + vrp.relations.select{ |r| r.type == :shipment }.each{ |r| + vrp.units.each{ |unit| + total_negative = 0 + total_positive = 0 + r.linked_services.each{ |s| + quantity = s.quantities.find{ |q| q.unit_id == unit.id }&.value || 0 + if quantity > 0 + total_positive += quantity + else + total_negative -= quantity + end + } + total_quantities[unit.id] += [total_positive, total_negative].max + } + } + + vrp.services.each{ |service| + next if service.relations.any?{ |r| r.type == :shipment } + + service.quantities.each{ |q| + total_quantities[q.unit.id] += (q.value || 0).abs + } + } vrp.services.each{ |service| service.quantities.each{ |quantity| @@ -284,7 +308,8 @@ def solve(vrp, job, thread_proc = nil, &block) OrtoolsVrp::Capacity.new( limit: (q&.limit && q.limit < 1e+22) ? q.limit : -1, overload_multiplier: q&.overload_multiplier || 0, - counting: unit&.counting || false + counting: unit&.counting || false, + initial_limit: q&.initial || [total_quantities[unit.id], q&.limit].compact.max ) }, time_window: OrtoolsVrp::TimeWindow.new( diff --git a/wrappers/ortools_vrp_pb.rb b/wrappers/ortools_vrp_pb.rb index 7328a2524..696c50d25 100644 --- a/wrappers/ortools_vrp_pb.rb +++ b/wrappers/ortools_vrp_pb.rb @@ -40,6 +40,8 @@ optional :limit, :float, 1 optional :overload_multiplier, :float, 2 optional :counting, :bool, 3 + optional :initial_limit, :float, 4 + optional :initial_capacity, :float, 5 end add_message 'ortools_vrp.Vehicle' do optional :id, :string, 1 diff --git a/wrappers/vroom.rb b/wrappers/vroom.rb index f1ca64c52..d3479a9e2 100644 --- a/wrappers/vroom.rb +++ b/wrappers/vroom.rb @@ -56,6 +56,7 @@ def solver_constraints :assert_matrices_only_one, :assert_no_distance_limitation, :assert_no_service_duration_modifiers, + :assert_vehicles_no_capacity_initial, :assert_vehicles_no_duration_limit, :assert_vehicles_no_force_start, :assert_vehicles_no_late_multiplier_or_single_vehicle, diff --git a/wrappers/wrapper.rb b/wrappers/wrapper.rb index 899ec9cb4..dccc5c52f 100644 --- a/wrappers/wrapper.rb +++ b/wrappers/wrapper.rb @@ -54,7 +54,7 @@ def assert_vehicles_start_or_end(vrp) def assert_vehicles_no_capacity_initial(vrp) vrp.vehicles.none?{ |vehicle| - vehicle.capacities.find{ |c| c.initial&.positive? } + vehicle.capacities.any?{ |c| c.initial && c.initial < c.limit } } end