From 8f554685c6a72d86c23fce3498c2237d1db4d369 Mon Sep 17 00:00:00 2001 From: Erel Segal-Halevi Date: Sun, 16 Jun 2024 10:30:41 +0300 Subject: [PATCH] ilp_avg --- prtpy/VERSION | 2 +- prtpy/__init__.py | 1 + prtpy/partitioning/complete_greedy.py | 23 +++++++++++------- prtpy/partitioning/integer_programming.py | 20 +++++++++------- prtpy/partitioning/integer_programming_avg.py | 24 +++++++------------ 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/prtpy/VERSION b/prtpy/VERSION index ee94dd8..b60d719 100644 --- a/prtpy/VERSION +++ b/prtpy/VERSION @@ -1 +1 @@ -0.8.3 +0.8.4 diff --git a/prtpy/__init__.py b/prtpy/__init__.py index 1f7f32d..bf02b50 100644 --- a/prtpy/__init__.py +++ b/prtpy/__init__.py @@ -20,6 +20,7 @@ class partitioning: from prtpy.partitioning.integer_programming import optimal as ilp from prtpy.partitioning.integer_programming import optimal as integer_programming + from prtpy.partitioning.integer_programming_avg import optimal as ilp_avg from prtpy.partitioning.greedy import greedy from prtpy.partitioning.greedy import greedy as lpt diff --git a/prtpy/partitioning/complete_greedy.py b/prtpy/partitioning/complete_greedy.py index e1499fc..7fcc94b 100644 --- a/prtpy/partitioning/complete_greedy.py +++ b/prtpy/partitioning/complete_greedy.py @@ -23,7 +23,8 @@ def anytime( - binner: Binner, numbins: int, items: List[int], relative_value: List[int] = None, + binner: Binner, numbins: int, items: List[int], + entitlements: List[int] = None, objective: obj.Objective = obj.MinimizeDifference, use_lower_bound: bool = True, # Prune branches whose lower bound (= optimistic value) is at least as large as the current minimum. @@ -64,10 +65,14 @@ def anytime( Bin #0: [46, 10], sum=56.0 Bin #1: [27, 16, 13], sum=56.0 Bin #2: [39, 26], sum=65.0 + + # Minimize the distance to the average - equal rights: >>> printbins(anytime(BinnerKeepingContents(), 3, walter_numbers, objective=obj.MinimizeDistAvg)) Bin #0: [39, 16], sum=55.0 Bin #1: [46, 13], sum=59.0 Bin #2: [27, 26, 10], sum=63.0 + + # Minimize the distance to the average - different rights: >>> printbins(anytime(BinnerKeepingContents(), 3, walter_numbers,[0.2,0.4,0.4], objective=obj.MinimizeDistAvg)) Bin #0: [27, 10], sum=37.0 Bin #1: [39, 16, 13], sum=68.0 @@ -171,9 +176,9 @@ def anytime( # we add a sum to each bin in order to equal them out to the bin with the highest relative value # (at the end of the algorithm we will remove these sums). first_bins = binner.new_bins(numbins) - if (relative_value): + if (entitlements): for i in range(numbins): - binner.add_item_to_bin(first_bins, (max(relative_value) * sum(items) - relative_value[i] * sum(items)), i) + binner.add_item_to_bin(first_bins, (max(entitlements) * sum(items) - entitlements[i] * sum(items)), i) first_vertex = (first_bins, 0) stack: List[Tuple[BinsArray, int]] = [first_vertex] if use_set_of_seen_states: @@ -250,13 +255,13 @@ def anytime( new_smallest_sum = current_sums[0] fast_lower_bound = -(new_smallest_sum + sum_of_remaining_items) elif objective == obj.MinimizeDistAvg: - if relative_value: + if entitlements: fast_lower_bound = 0 for i in range (numbins): - # For each bin: we take off the sum that we added in the beginning of the algorithm (max(relative_value) * sum(items) - relative_value[i] * sum(items)) - # Then we check if the difference between the bin's sum and the relative AVG for bin i: (sum(items)*relative_value[i]) + # For each bin: we take off the sum that we added in the beginning of the algorithm (max(entitlements) * sum(items) - entitlements[i] * sum(items)) + # Then we check if the difference between the bin's sum and the relative AVG for bin i: (sum(items)*entitlements[i]) # is positive and contributes to our final difference or negative and we will not add anything to our difference. - fast_lower_bound = fast_lower_bound + max((current_sums[i]-(max(relative_value) * sum(items) - relative_value[i] * sum(items)))-sum(items)*relative_value[i],0) + fast_lower_bound = fast_lower_bound + max((current_sums[i]-(max(entitlements) * sum(items) - entitlements[i] * sum(items)))-sum(items)*entitlements[i],0) else: fast_lower_bound = 0 avg = sum(items) / numbins @@ -269,7 +274,7 @@ def anytime( continue new_bins = binner.add_item_to_bin(binner.copy_bins(current_bins), next_item, bin_index) - if not relative_value: + if not entitlements: binner.sort_by_ascending_sum(new_bins) new_sums = tuple(binner.sums(new_bins)) @@ -298,7 +303,7 @@ def anytime( times_heuristic_3_activated) - if (relative_value): + if (entitlements): # For each bin we remove the value that we added in the beginning of the algorithm. for i in range(numbins): binner.remove_item_from_bin(best_bins, i, 0) diff --git a/prtpy/partitioning/integer_programming.py b/prtpy/partitioning/integer_programming.py index 27fe212..235bcea 100644 --- a/prtpy/partitioning/integer_programming.py +++ b/prtpy/partitioning/integer_programming.py @@ -24,7 +24,7 @@ def optimal( copies=1, time_limit=inf, additional_constraints:Callable=lambda sums:[], - weights:List[float]=None, + entitlements:List[float]=None, verbose=0, solver_name = mip.CBC, # passed to MIP. See https://docs.python-mip.com/en/latest/quickstart.html#creating-models. # solver_name = mip.GRB, # passed to MIP. See https://docs.python-mip.com/en/latest/quickstart.html#creating-models. @@ -42,7 +42,7 @@ def optimal( :param copies: how many copies there are of each item. Default: 1. :param time_limit: stop the computation after this number of seconds have passed. :param additional_constraints: a function that accepts the list of sums in ascending order, and returns a list of possible additional constraints on the sums. - :param weights: if given, must be of size bins.num. Divides each sum by its weight before applying the objective function. + :param entitlements: if given, must be of size bins.num. Divides each sum by its weight before applying the objective function. :param solver_name: passed to MIP. See https://docs.python-mip.com/en/latest/quickstart.html#creating-models :param model_filename: if not None, the MIP model will be written into this file, for debugging. NOTE: The extension should be either ".lp" or ".mps" (it indicates the output format) :param solution_filename: if not None, the solution will be written into this file, for debugging. @@ -76,11 +76,11 @@ def optimal( array([56., 56., 65.]) >>> items = [11.1, 11, 11, 11, 22] - >>> optimal(BinnerKeepingSums(), 2, items, objective=obj.MaximizeSmallestSum, weights=[1,1]) + >>> optimal(BinnerKeepingSums(), 2, items, objective=obj.MaximizeSmallestSum, entitlements=[1,1]) array([33. , 33.1]) - >>> optimal(BinnerKeepingSums(), 2, items, objective=obj.MaximizeSmallestSum, weights=[1,2]) + >>> optimal(BinnerKeepingSums(), 2, items, objective=obj.MaximizeSmallestSum, entitlements=[1,2]) array([22. , 44.1]) - >>> optimal(BinnerKeepingSums(), 2, items, objective=obj.MaximizeSmallestSum, weights=[10,2]) + >>> optimal(BinnerKeepingSums(), 2, items, objective=obj.MaximizeSmallestSum, entitlements=[10,2]) array([11.1, 55. ]) >>> from prtpy import partition @@ -103,13 +103,17 @@ def optimal( Bin #3: [62, 187], sum=249.0 Bin #4: [93, 158], sum=251.0 """ + if objective == obj.MinimizeDistAvg: + from prtpy.partitioning.integer_programming_avg import optimal as optimal_avg + return optimal_avg(binner, numbins, items, entitlements=entitlements, copies=copies, time_limit=time_limit, verbose=verbose, solver_name=solver_name, model_filename=model_filename, solution_filename=solution_filename) + ibins = range(numbins) items = list(items) iitems = range(len(items)) if isinstance(copies, Number): copies = {iitem: copies for iitem in iitems} - if weights is None: - weights = numbins*[1] + if entitlements is None: + entitlements = numbins*[1] model = mip.Model(name = '', solver_name=solver_name) counts: dict = { @@ -117,7 +121,7 @@ def optimal( for iitem in iitems } # counts[i][j] is a variable that represents how many times item i appears in bin j. bin_sums = [ - sum([counts[iitem][ibin] * binner.valueof(items[iitem]) for iitem in iitems])/weights[ibin] + sum([counts[iitem][ibin] * binner.valueof(items[iitem]) for iitem in iitems])/entitlements[ibin] for ibin in ibins ] # bin_sums[j] is a variable-expression that represents the sum of values in bin j. diff --git a/prtpy/partitioning/integer_programming_avg.py b/prtpy/partitioning/integer_programming_avg.py index efc060b..3520e1e 100644 --- a/prtpy/partitioning/integer_programming_avg.py +++ b/prtpy/partitioning/integer_programming_avg.py @@ -15,7 +15,7 @@ import mip def optimal( - binner: Binner, numbins: int, items: List[any], relative_values: List[any] = None, + binner: Binner, numbins: int, items: List[any], entitlements: List[any] = None, copies=1, time_limit=inf, verbose=0, @@ -29,7 +29,7 @@ def optimal( :param numbins: number of bins. :param items: list of items. - :param relative_values: list of relative values that sum up to 1 for the bins if there are any. + :param entitlements: list of relative values that sum up to 1 for the bins if there are any. :param copies: how many copies there are of each item. Default: 1. :param time_limit: stop the computation after this number of seconds have passed. :param valueof: a function that maps an item from the list `items` to a number representing its value. @@ -101,16 +101,11 @@ def optimal( for i in range (len(items)): sum_items = sum_items + items[i] * copies[i] - if relative_values: - z_js = [ - bin_sums[ibin] - sum_items * relative_values[ibin] - for ibin in ibins - ] - else: - z_js = [ - bin_sums[ibin] - sum_items / len(ibins) - for ibin in ibins - ] + effective_entitlements = entitlements or [1. / numbins for ibin in ibins] + z_js = [ + bin_sums[ibin] - sum_items * effective_entitlements[ibin] + for ibin in ibins + ] t_js = [ model.add_var(var_type=mip.INTEGER) for ibin in ibins @@ -147,7 +142,7 @@ def optimal( count_item_in_bin = int(counts[iitem][ibin].x) for _ in range(count_item_in_bin): binner.add_item_to_bin(output, items[iitem], ibin) - if not relative_values: + if not entitlements: binner.sort_by_ascending_sum(output) if solution_filename is not None: @@ -163,5 +158,4 @@ def optimal( if __name__ == "__main__": import doctest, logging - (failures, tests) = doctest.testmod(report=True, optionflags=doctest.FAIL_FAST) - print("{} failures, {} tests".format(failures, tests)) + print(doctest.testmod(report=True, optionflags=doctest.FAIL_FAST))