diff --git a/README.md b/README.md index 19b3c6f..fd1b27d 100644 --- a/README.md +++ b/README.md @@ -1473,6 +1473,15 @@ routing.solve( ) ``` +Native improvement-rate stopping is available through search parameters: + +```ruby +search_parameters.improvement_limit_parameters = { + improvement_rate_coefficient: 0.01, + improvement_rate_solutions_distance: 5 +} +``` + ### Solution Tracing ```ruby diff --git a/ext/or-tools/routing.cpp b/ext/or-tools/routing.cpp index 8455327..f953898 100644 --- a/ext/or-tools/routing.cpp +++ b/ext/or-tools/routing.cpp @@ -397,6 +397,32 @@ void init_routing(Rice::Module& m) { "lns_time_limit=", [](RoutingSearchParameters& self, int64_t value) { self.mutable_lns_time_limit()->set_seconds(value); + }) + .define_method( + "improvement_limit_parameters", + [](RoutingSearchParameters& self) -> Object { + if (!self.has_improvement_limit_parameters()) { + return Object(Qnil); + } + + const auto& parameters = self.improvement_limit_parameters(); + Hash result; + result[Symbol("improvement_rate_coefficient")] = + parameters.improvement_rate_coefficient(); + result[Symbol("improvement_rate_solutions_distance")] = + parameters.improvement_rate_solutions_distance(); + return result; + }) + .define_method( + "improvement_limit_parameters=", + [](RoutingSearchParameters& self, Hash value) { + const double coefficient = + value.get(Symbol("improvement_rate_coefficient")); + const int distance = + value.get(Symbol("improvement_rate_solutions_distance")); + auto* parameters = self.mutable_improvement_limit_parameters(); + parameters->set_improvement_rate_coefficient(coefficient); + parameters->set_improvement_rate_solutions_distance(distance); }); Rice::define_class_under(m, "RoutingIndexManager") diff --git a/test/routing_solution_trace_test.rb b/test/routing_solution_trace_test.rb index 6131121..4d6a95a 100644 --- a/test/routing_solution_trace_test.rb +++ b/test/routing_solution_trace_test.rb @@ -101,6 +101,28 @@ def test_keeps_aggregates_after_samples_are_truncated assert_operator values[:improvement_count], :>, 1 end + def test_native_improvement_limit_stops_search + baseline_routing = build_routing + baseline_trace = baseline_routing.enable_solution_trace + baseline_routing.solve_with_parameters(search_parameters(solution_limit: 500)) + + routing = build_routing + trace = routing.enable_solution_trace + solution = routing.solve_with_parameters( + search_parameters( + solution_limit: 500, + improvement_limit_parameters: { + improvement_rate_coefficient: 0.01, + improvement_rate_solutions_distance: 1 + } + ) + ) + + assert_equal 500, baseline_trace.to_h[:solution_count] + assert_operator trace.to_h[:solution_count], :<, 500 + assert_equal solution.objective_value, trace.to_h[:best_solution_objective] + end + def test_resets_for_warm_solves routing = build_routing trace = routing.enable_solution_trace( @@ -239,12 +261,13 @@ def build_routing routing end - def search_parameters(solution_limit: nil, time_limit: nil) + def search_parameters(solution_limit: nil, time_limit: nil, improvement_limit_parameters: nil) parameters = ORTools.default_routing_search_parameters parameters.first_solution_strategy = :path_cheapest_arc parameters.local_search_metaheuristic = :guided_local_search parameters.solution_limit = solution_limit if solution_limit parameters.time_limit = time_limit if time_limit + parameters.improvement_limit_parameters = improvement_limit_parameters if improvement_limit_parameters parameters end diff --git a/test/routing_test.rb b/test/routing_test.rb index 9f9fe7c..9a7074e 100644 --- a/test/routing_test.rb +++ b/test/routing_test.rb @@ -907,6 +907,20 @@ def test_search_parameters search_parameters.first_solution_strategy = :path_cheapest_arc search_parameters.local_search_metaheuristic = :guided_local_search search_parameters.log_search = true + assert_nil search_parameters.improvement_limit_parameters + + search_parameters.improvement_limit_parameters = { + improvement_rate_coefficient: 0.01, + improvement_rate_solutions_distance: 5 + } + + assert_equal( + { + improvement_rate_coefficient: 0.01, + improvement_rate_solutions_distance: 5 + }, + search_parameters.improvement_limit_parameters + ) end def test_set_allowed_vehicles_for_index