From 626c013f2aafc4c5839db92057456bc94f901802 Mon Sep 17 00:00:00 2001 From: Florian Fontan Date: Sat, 3 Oct 2026 00:46:54 +0200 Subject: [PATCH] irregular: fix the local search on infeasible sub-instances With 'use_local_search', BinPackingWithLeftovers (through 'sequential_feasibility') solves Feasibility sub-instances with bins narrower than needed. On such a sub-instance, 'pack_item' kept solving the shrinkage LP until the time limit; the LP then interrupted by the time limit had no solution, which made 'linear_programming_minimize_shrinkage' throw ('wrong LP solution'). - 'linear_programming_minimize_shrinkage' and 'linear_programming_anchor' stop and keep the current solution when the time limit is reached. If the LP isn't solved to optimality otherwise, they throw with the model status ('exit(1)' on an infeasible LP is removed). - 'pack_item' gives up if the item fits in no bin of the sub-instance, or if the selected bin has no free space left (the item used to be placed inside another one, which made 'find_best_edge_separator' throw). - In the non-anytime modes, 'pack_item' also gives up after 'OptimizeParameters::not_anytime_local_search_maximum_number_of_iterations_without_improvement' (100) iterations without increasing the sum of the scale factors of the items. In anytime mode, it keeps trying until the time limit. - The bin bounds were scaled twice in 'linear_programming_minimize_shrinkage' ('aabb_scaled * scale_value'), so they didn't restrict the items, and mixed scaled and original units in 'linear_programming_anchor'. - Remove debugging leftovers: the files written in the working directory ('initial_solution.txt' at each LP, 'infeasible.mps', 'tmp.json') and the outputs printed on the standard output. --- include/packingsolver/irregular/optimize.hpp | 6 + python/src/irregular.cpp | 1 + python/tests/test_irregular.py | 34 +++++ src/irregular/linear_programming.cpp | 136 ++++--------------- src/irregular/local_search.cpp | 52 +++++-- src/irregular/local_search.hpp | 8 ++ src/irregular/main.cpp | 3 + src/irregular/optimize.cpp | 6 + 8 files changed, 124 insertions(+), 122 deletions(-) diff --git a/include/packingsolver/irregular/optimize.hpp b/include/packingsolver/irregular/optimize.hpp index 70a5eafea..99107df58 100644 --- a/include/packingsolver/irregular/optimize.hpp +++ b/include/packingsolver/irregular/optimize.hpp @@ -246,6 +246,12 @@ struct OptimizeParameters: packingsolver::Parameters /** Number of iterations of the sequential value correction algorithm. */ Counter not_anytime_sequential_value_correction_number_of_iterations = 32; + /** + * Maximum number of iterations without improvement when the local search + * packs an item, after which the item is considered not to fit. + */ + Counter not_anytime_local_search_maximum_number_of_iterations_without_improvement = 100; + /** * Size of the queue in the bin packing subproblem of the dichotomic search * algorithm. diff --git a/python/src/irregular.cpp b/python/src/irregular.cpp index 9fc7c306d..c96c054dc 100644 --- a/python/src/irregular.cpp +++ b/python/src/irregular.cpp @@ -671,6 +671,7 @@ void bind_irregular(nb::module_& m) .def_rw("not_anytime_tree_search_periodic_packing_queue_size", &OptimizeParameters::not_anytime_tree_search_periodic_packing_queue_size) .def_rw("not_anytime_sequential_single_knapsack_subproblem_tree_search_queue_size", &OptimizeParameters::not_anytime_sequential_single_knapsack_subproblem_tree_search_queue_size) .def_rw("not_anytime_sequential_value_correction_number_of_iterations", &OptimizeParameters::not_anytime_sequential_value_correction_number_of_iterations) + .def_rw("not_anytime_local_search_maximum_number_of_iterations_without_improvement", &OptimizeParameters::not_anytime_local_search_maximum_number_of_iterations_without_improvement) .def_rw("not_anytime_dichotomic_search_subproblem_tree_search_queue_size", &OptimizeParameters::not_anytime_dichotomic_search_subproblem_tree_search_queue_size) .def_rw("reduction_parameters", &OptimizeParameters::reduction_parameters); diff --git a/python/tests/test_irregular.py b/python/tests/test_irregular.py index eeed7b206..c8cb918e4 100644 --- a/python/tests/test_irregular.py +++ b/python/tests/test_irregular.py @@ -529,6 +529,9 @@ def test_parameters(): parameters.use_tree_search = True parameters.not_anytime_tree_search_queue_size = 64 assert parameters.not_anytime_tree_search_queue_size == 64 + assert parameters.not_anytime_local_search_maximum_number_of_iterations_without_improvement == 100 + parameters.not_anytime_local_search_maximum_number_of_iterations_without_improvement = 10 + assert parameters.not_anytime_local_search_maximum_number_of_iterations_without_improvement == 10 parameters.initial_maximum_approximation_ratio = 0.1 assert parameters.initial_maximum_approximation_ratio == pytest.approx(0.1) parameters.reduction_parameters.reduce = False @@ -538,6 +541,37 @@ def test_parameters(): assert output.solution.number_of_bins() == 2 +@pytest.mark.parametrize("copies", [1, 4]) +@pytest.mark.parametrize("reduce", [True, False]) +def test_local_search_bin_packing_with_leftovers(copies, reduce, tmp_path, monkeypatch): + """The local search used to loop on the infeasible sub-instances of the + leftovers, until an LP interrupted by the time limit made it throw ('wrong + LP solution'). It also used to write files in the working directory.""" + monkeypatch.chdir(tmp_path) + instance_builder = psi.InstanceBuilder() + instance_builder.set_objective(psi.Objective.BinPackingWithLeftovers) + instance_builder.add_bin_type(square(10)) + instance_builder.add_item_type(item_shapes(square(5)), copies=copies) + parameters = quiet_parameters(use_local_search=True, use_tree_search=False) + parameters.reduction_parameters.reduce = reduce + output = psi.optimize(instance_builder.build(), parameters) + assert output.solution.feasible() + assert output.solution.full() + assert list(tmp_path.iterdir()) == [] + + +def test_local_search_full_bin(): + """An item assigned to a bin without free space used to be placed inside + another item, which made the local search throw ('violated separation + constraint').""" + instance_builder = psi.InstanceBuilder() + instance_builder.read(os.path.join(DATA_DIR, "multiple_bins.json")) + parameters = quiet_parameters(use_local_search=True, use_tree_search=False) + output = psi.optimize(instance_builder.build(), parameters) + assert output.solution.feasible() + assert output.solution.full() + + def test_lifetimes(): """Outputs and solutions stay valid after their instance is dropped.""" output = psi.optimize(bin_packing_instance(5), quiet_parameters()) diff --git a/src/irregular/linear_programming.cpp b/src/irregular/linear_programming.cpp index ea10476cf..5862e4f24 100644 --- a/src/irregular/linear_programming.cpp +++ b/src/irregular/linear_programming.cpp @@ -3,8 +3,6 @@ #include "irregular/utils.hpp" #include "irregular/solution_builder.hpp" -#include "shape/writer.hpp" - #ifdef CBC_FOUND #include "mathoptsolverscmake/mathopt_cbc.hpp" #endif @@ -165,25 +163,6 @@ EdgeSeparationConstraintParameters packingsolver::irregular::find_best_edge_sepa + output.coef_lambda2 * scale_2; //if (shape::strictly_lesser(value, 0.0)) { if (shape::strictly_lesser(value, -1e-6)) { - std::cout << "shift_1 " << shift_1.to_string() - << " shift_2 " << shift_2.to_string() << std::endl; - std::cout << "scale_1 " << scale_1 - << " scale_2 " << scale_2 - << std::endl; - Shape shape_1_shifted = shape_1; - Shape shape_2_shifted = shape_2; - shape_1_shifted.shift(shift_1.x, shift_1.y); - shape_2_shifted.shift(shift_2.x, shift_2.y); - Shape shape_1_scaled = scale_1 * shape_1; - Shape shape_2_scaled = scale_2 * shape_2; - shape_1_scaled.shift(shift_1.x, shift_1.y); - shape_2_scaled.shift(shift_2.x, shift_2.y); - Writer() - .add_shape(shape_1) - .add_shape(shape_1_scaled) - .add_shape(shape_2) - .add_shape(shape_2_scaled) - .write_json("tmp.json"); const ShapeElement& edge_element = ((output.edge_shape_pos == 0)? shape_1.elements[output.edge_element_pos]: shape_2.elements[output.edge_element_pos]); @@ -324,10 +303,10 @@ Solution linear_programming_anchor( item_var_pos < (ItemPos)unfixed_items.size(); ++item_var_pos) { const AxisAlignedBoundingBox& ia = unfixed_item_aabbs[item_var_pos].item_aabb; - LengthDbl x_min = (x_weight < 0)? solution.x_min(): bin_type.aabb_scaled.x_min; - LengthDbl x_max = (x_weight > 0)? solution.x_max(): bin_type.aabb_scaled.x_max; - LengthDbl y_min = (y_weight < 0)? solution.y_min(): bin_type.aabb_scaled.y_min; - LengthDbl y_max = (y_weight > 0)? solution.y_max(): bin_type.aabb_scaled.y_max; + LengthDbl x_min = (x_weight < 0)? solution.x_min(): bin_type.aabb_orig.x_min; + LengthDbl x_max = (x_weight > 0)? solution.x_max(): bin_type.aabb_orig.x_max; + LengthDbl y_min = (y_weight < 0)? solution.y_min(): bin_type.aabb_orig.y_min; + LengthDbl y_max = (y_weight > 0)? solution.y_max(): bin_type.aabb_orig.y_max; item_bin_bounds[item_var_pos].x_min = x_min * instance.parameters().scale_value - ia.x_min; item_bin_bounds[item_var_pos].x_max = x_max * instance.parameters().scale_value - ia.x_max; item_bin_bounds[item_var_pos].y_min = y_min * instance.parameters().scale_value - ia.y_min; @@ -575,6 +554,14 @@ Solution linear_programming_anchor( //std::cout << "LP solve start" << std::endl; mathoptsolverscmake::solve(highs); //std::cout << "LP solve end" << std::endl; + if (highs.getModelStatus() != HighsModelStatus::kOptimal) { + // If the time limit is reached, keep the current solution. + if (parameters.timer.needs_to_end()) + break; + throw std::runtime_error( + FUNC_SIGNATURE + ": LP not solved to optimality; " + "model status: " + highs.modelStatusToString(highs.getModelStatus()) + "."); + } lp_solution = mathoptsolverscmake::get_solution(highs); #else throw std::invalid_argument(FUNC_SIGNATURE); @@ -842,10 +829,10 @@ LinearProgrammingMinimizeShrinkageOutput packingsolver::irregular::linear_progra unfixed_item_aabbs[item_var_pos].part_movement_aabbs[shape_pos][part_pos]; // RHS: part movement AABB clamped to bin bounds. - const LengthDbl x_min = (std::max)(pm.x_min, bin_type.aabb_scaled.x_min * sv); - const LengthDbl x_max = (std::min)(pm.x_max, bin_type.aabb_scaled.x_max * sv); - const LengthDbl y_min = (std::max)(pm.y_min, bin_type.aabb_scaled.y_min * sv); - const LengthDbl y_max = (std::min)(pm.y_max, bin_type.aabb_scaled.y_max * sv); + const LengthDbl x_min = (std::max)(pm.x_min, bin_type.aabb_scaled.x_min); + const LengthDbl x_max = (std::min)(pm.x_max, bin_type.aabb_scaled.x_max); + const LengthDbl y_min = (std::max)(pm.y_min, bin_type.aabb_scaled.y_min); + const LengthDbl y_max = (std::min)(pm.y_max, bin_type.aabb_scaled.y_max); const std::string sid = std::to_string(item_pos) + "_s" + std::to_string(shape_pos) @@ -1122,13 +1109,14 @@ LinearProgrammingMinimizeShrinkageOutput packingsolver::irregular::linear_progra highs.setOptionValue("parallel", "off"); mathoptsolverscmake::load(highs, lp_model); //mathoptsolverscmake::write_mps(highs, "lp.mps"); - lp_model.write_solution(lp_initial_solution, "initial_solution.txt"); mathoptsolverscmake::solve(highs); - if (highs.getModelStatus() == HighsModelStatus::kInfeasible - || highs.getModelStatus() == HighsModelStatus::kUnboundedOrInfeasible) { - highs.writeModel("infeasible.mps"); - std::cerr << "linear_programming_minimize_shrinkage: LP infeasible, wrote infeasible.mps" << std::endl; - exit(1); + if (highs.getModelStatus() != HighsModelStatus::kOptimal) { + // If the time limit is reached, keep the current solution. + if (parameters.timer.needs_to_end()) + break; + throw std::runtime_error( + FUNC_SIGNATURE + ": LP not solved to optimality; " + "model status: " + highs.modelStatusToString(highs.getModelStatus()) + "."); } lp_solution = mathoptsolverscmake::get_solution(highs); #else @@ -1139,7 +1127,6 @@ LinearProgrammingMinimizeShrinkageOutput packingsolver::irregular::linear_progra } if (!lp_model.check_solution(lp_solution, 0)) { - lp_model.check_solution(lp_solution, 4); throw std::logic_error( FUNC_SIGNATURE + ": wrong LP solution."); } @@ -1237,86 +1224,13 @@ LinearProgrammingMinimizeShrinkageOutput packingsolver::irregular::linear_progra + "_i" + std::to_string(e.item_2_var_pos) + "_s" + std::to_string(e.item_shape_2_pos) + "_p" + std::to_string(e.item_part_2_pos); - std::cout << "constraint " << constraint_name << std::endl; - - std::cout << "item_1_pos " << item_1_pos - << " item_type_1 " << item_1_prev.item_type_id - << " item_1_shape_pos " << e.item_shape_1_pos - << " item_1_part_pos " << e.item_part_1_pos - << " angle " << item_1_prev.angle - << " mirror " << item_1_prev.mirror - << std::endl; - std::cout << " bl " << (sv * item_1_prev.bl_corner).to_string() - << " -> " << (sv * item_1_curr.bl_corner).to_string() << std::endl; - std::cout << " lambda " << current_lambda[item_1_pos] - << " -> " << new_lambda[item_1_pos] << std::endl; - std::cout << "item_2_pos " << item_2_pos - << " item_type_2 " << item_2_prev.item_type_id - << " item_2_shape_pos " << e.item_shape_2_pos - << " item_2_part_pos " << e.item_part_2_pos - << " angle " << item_2_prev.angle - << " mirror " << item_2_prev.mirror - << std::endl; - std::cout << " bl " << (sv * item_2_prev.bl_corner).to_string() - << " -> " << (sv * item_2_curr.bl_corner).to_string() << std::endl; - std::cout << " lambda " << current_lambda[item_2_pos] - << " -> " << new_lambda[item_2_pos] << std::endl; - - const EdgeSeparationConstraintParameters p = find_best_edge_separator( - convex_part_1, - sv * item_1_prev.bl_corner, - current_lambda[item_1_pos], - convex_part_2, - sv * item_2_prev.bl_corner, - current_lambda[item_2_pos]); - std::cout << "shape_pos " << p.edge_shape_pos - << " edge_element_pos " << p.edge_element_pos - << " point_element_pos " << p.point_element_pos - << " distance " << p.distance - << std::endl; - std::cout << "edge_element " << ((p.edge_shape_pos == 0)? - convex_part_1.elements[p.edge_element_pos].to_string(): - convex_part_2.elements[p.edge_element_pos].to_string()) << std::endl; - std::cout << "point_element " << ((p.edge_shape_pos == 0)? - convex_part_2.elements[p.point_element_pos].to_string(): - convex_part_1.elements[p.point_element_pos].to_string()) << std::endl; - std::cout << "point " << p.point.to_string() << std::endl; - std::cout << "coef_x1 " << p.coef_x1 << " coef_y1 " << p.coef_y1 << " coef_lambda1 " << p.coef_lambda1 << std::endl; - std::cout << "coef_x2 " << p.coef_x2 << " coef_y2 " << p.coef_y2 << " coef_lambda2 " << p.coef_lambda2 << std::endl; - - Writer() - .add_shape(convex_part_1, "Part 1") - .add_shape(convex_part_2, "Part 2") - .add_shape(convex_part_1_prev, "Part 1 (prev)") - .add_shape(convex_part_1_curr, "Part 1 (curr)") - .add_shape(convex_part_2_prev, "Part 2 (prev)") - .add_shape(convex_part_2_curr, "Part 2 (curr)") - .write_json("tmp.json"); throw std::logic_error( - FUNC_SIGNATURE + ": convex part intersection after LP."); + FUNC_SIGNATURE + ": convex part intersection after LP; " + "constraint: " + constraint_name + "."); } Solution::OverlappingItems overlapping_items = new_solution.compute_overlapping_items(0, &new_lambda); if (!overlapping_items.item_item_pairs.empty()) { - const SolutionBin& bin = new_solution.bin(bin_pos); - for (const auto& pair: overlapping_items.item_item_pairs) { - const SolutionItem& item_1 = bin.items[pair.first]; - const ItemType& item_type_1 = instance.item_type(item_1.item_type_id); - ShapeWithHoles shape_1 = new_lambda[pair.first] * new_solution.shape_scaled(0, pair.first, 0); - ShapeWithHoles shape_1_prev = current_lambda[pair.first] * solution.shape_scaled(0, pair.first, 0); - - const SolutionItem& item_2 = bin.items[pair.second]; - const ItemType& item_type_2 = instance.item_type(item_2.item_type_id); - ShapeWithHoles shape_2 = new_lambda[pair.second] * new_solution.shape_scaled(0, pair.second, 0); - ShapeWithHoles shape_2_prev = current_lambda[pair.second] * solution.shape_scaled(0, pair.second, 0); - - Writer() - .add_shape_with_holes(shape_1_prev, "Shape 1 (prev)") - .add_shape_with_holes(shape_1, "Shape 1 (curr)") - .add_shape_with_holes(shape_2_prev, "Shape 2 (prev)") - .add_shape_with_holes(shape_2, "Shape 2 (curr)") - .write_json("tmp.json"); - } throw std::logic_error( FUNC_SIGNATURE + ": infeasible new_solution after LP."); } diff --git a/src/irregular/local_search.cpp b/src/irregular/local_search.cpp index b7a8bb1bd..9701421be 100644 --- a/src/irregular/local_search.cpp +++ b/src/irregular/local_search.cpp @@ -36,7 +36,8 @@ struct LocalSearchBinData * difference of the bin AABB minus existing item shapes, defects, and * borders). The bin is the valid one (item AABB fits) with the most remaining * area; falls back to the bin with the most remaining area if none fits. - * Returns the bin position the item was placed in. + * Returns the bin position the item was placed in, or -1 if the selected bin + * has no free space left (the item is then not added). */ BinPos assign_item_to_bin( const Instance& instance, @@ -132,14 +133,12 @@ BinPos assign_item_to_bin( } } - // Fall back to the bin centre if no free region was found. - Point bl_corner; - if (best_region_pos != -1) { - bl_corner = free_regions[best_region_pos].find_point_strictly_inside(); - } else { - bl_corner.x = (instance.parameters().scale_value * bin_aabb.x_min + instance.parameters().scale_value * bin_aabb.x_max) / 2.0; - bl_corner.y = (instance.parameters().scale_value * bin_aabb.y_min + instance.parameters().scale_value * bin_aabb.y_max) / 2.0; - } + // If the bin has no free space left, the item can't be placed in it: a + // point inside another item can't be separated from it by the + // shrinkage LP. + if (best_region_pos == -1) + return -1; + Point bl_corner = free_regions[best_region_pos].find_point_strictly_inside(); //std::cout << "bl_corner " << bl_corner.to_string() << std::endl; bl_corner = 1.0 / instance.parameters().scale_value * bl_corner; //std::cout << "bl_corner " << bl_corner.to_string() << std::endl; @@ -166,8 +165,11 @@ BinPos assign_item_to_bin( /** * Assign an item to a bin, then run minimize_shrinkage on that bin until it * is feasible (all items at full scale). Updates solution and bin_data in - * place. Returns false if the timer or end-flag fired before feasibility was - * reached. + * place. Returns false if the item fits in no bin, if feasibility wasn't + * reached within + * 'LocalSearchParameters::maximum_number_of_iterations_without_improvement' + * iterations without improvement, or if the timer or end-flag fired before + * feasibility was reached. */ bool pack_item( const Instance& instance, @@ -181,10 +183,17 @@ bool pack_item( if (algorithm_formatter.end_boolean() || parameters.timer.needs_to_end()) return false; + // 'optimize_item_types_fit' only checks the original instance: the bins + // of the sub-instances built by 'sequential_feasibility' are narrower. + if (!instance.fits_some_bin(item_type_id)) + return false; + const ItemType& item_type = instance.item_type(item_type_id); const BinPos bin_pos = assign_item_to_bin( instance, rng, item_type_id, solution, bin_data); + if (bin_pos == -1) + return false; bin_data[bin_pos].remaining_area -= item_type.area_scaled; bin_data[bin_pos].item_penalties.push_back(1.0); @@ -194,9 +203,18 @@ bool pack_item( bin_solution.append_bin(solution, bin_pos, 1); bool bin_feasible = false; + // Sum of the scale factors of the items of the bin: it measures how far + // the bin is from being feasible. + double best_lambda_sum = -1; + Counter number_of_iterations_without_improvement = 0; while (!bin_feasible) { if (algorithm_formatter.end_boolean() || parameters.timer.needs_to_end()) return false; + if (parameters.maximum_number_of_iterations_without_improvement != -1 + && number_of_iterations_without_improvement + >= parameters.maximum_number_of_iterations_without_improvement) { + return false; + } LinearProgrammingMinimizeShrinkageParameters lp_params; lp_params.timer = parameters.timer; @@ -211,6 +229,16 @@ bool pack_item( bin_feasible = lp_output.feasible; bin_data[bin_pos].lambda = lp_output.final_lambda; + double lambda_sum = 0; + for (double lambda: lp_output.final_lambda) + lambda_sum += lambda; + if (shape::strictly_greater(lambda_sum, best_lambda_sum)) { + best_lambda_sum = lambda_sum; + number_of_iterations_without_improvement = 0; + } else { + number_of_iterations_without_improvement++; + } + for (ItemPos item_pos = 0; item_pos < (ItemPos)lp_output.items_shrunken.size(); ++item_pos) { @@ -254,6 +282,8 @@ LocalSearchOutput packingsolver::irregular::local_search( inner_parameters.timer = parameters.timer; inner_parameters.timer.add_end_boolean(&algorithm_formatter.end_boolean()); inner_parameters.seed = parameters.seed; + inner_parameters.maximum_number_of_iterations_without_improvement + = parameters.maximum_number_of_iterations_without_improvement; return local_search(sub_instance, inner_parameters).solution_pool; }; diff --git a/src/irregular/local_search.hpp b/src/irregular/local_search.hpp index 1169dd0c0..f990bc6af 100644 --- a/src/irregular/local_search.hpp +++ b/src/irregular/local_search.hpp @@ -25,6 +25,14 @@ struct LocalSearchParameters: packingsolver::Parameters(), "") ("not-anytime-sequential-single-knapsack-subproblem-tree-search-queue-size,", po::value(), "") ("not-anytime-sequential-value-correction-number-of-iterations,", po::value(), "") + ("not-anytime-local-search-maximum-number-of-iterations-without-improvement,", po::value(), "") ("not-anytime-dichotomic-search-subproblem-tree-search-queue-size,", po::value(), "") ("group-identical-bins,", po::value(), "") @@ -193,6 +194,8 @@ int main(int argc, char *argv[]) parameters.not_anytime_sequential_single_knapsack_subproblem_tree_search_queue_size = vm["not-anytime-sequential-single-knapsack-subproblem-tree-search-queue-size"].as(); if (vm.count("not-anytime-sequential-value-correction-number-of-iterations")) parameters.not_anytime_sequential_value_correction_number_of_iterations = vm["not-anytime-sequential-value-correction-number-of-iterations"].as(); + if (vm.count("not-anytime-local-search-maximum-number-of-iterations-without-improvement")) + parameters.not_anytime_local_search_maximum_number_of_iterations_without_improvement = vm["not-anytime-local-search-maximum-number-of-iterations-without-improvement"].as(); if (vm.count("not-anytime-dichotomic-search-subproblem-tree-search-queue-size")) parameters.not_anytime_dichotomic_search_subproblem_tree_search_queue_size = vm["not-anytime-dichotomic-search-subproblem-tree-search-queue-size"].as(); const irregular::Output output = optimize(instance, parameters); diff --git a/src/irregular/optimize.cpp b/src/irregular/optimize.cpp index 1e1ec9d5e..5914ec4ee 100644 --- a/src/irregular/optimize.cpp +++ b/src/irregular/optimize.cpp @@ -319,6 +319,8 @@ void optimize_tree_search( last_bin_parameters.optimization_mode = parameters.optimization_mode; last_bin_parameters.not_anytime_maximum_approximation_ratio = parameters.not_anytime_maximum_approximation_ratio; last_bin_parameters.not_anytime_tree_search_queue_size = parameters.not_anytime_tree_search_queue_size; + last_bin_parameters.not_anytime_local_search_maximum_number_of_iterations_without_improvement + = parameters.not_anytime_local_search_maximum_number_of_iterations_without_improvement; last_bin_parameters.tree_search_guides = {2, 3}; last_bin_parameters.linear_programming_solver_name = parameters.linear_programming_solver_name; // Respect the caller's explicit algorithm selection (if any), rather @@ -370,6 +372,10 @@ void optimize_local_search( LocalSearchParameters ls_parameters; ls_parameters.verbosity_level = 0; ls_parameters.timer = parameters.timer; + if (parameters.optimization_mode != OptimizationMode::Anytime) { + ls_parameters.maximum_number_of_iterations_without_improvement + = parameters.not_anytime_local_search_maximum_number_of_iterations_without_improvement; + } ls_parameters.new_solution_callback = [&algorithm_formatter, local_output]( const irregular::Output& ps_output) {