Skip to content

Commit

Permalink
ilp_avg
Browse files Browse the repository at this point in the history
  • Loading branch information
erelsgl committed Jun 16, 2024
1 parent 6f7d09e commit 8f55468
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 33 deletions.
2 changes: 1 addition & 1 deletion prtpy/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.3
0.8.4
1 change: 1 addition & 0 deletions prtpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 14 additions & 9 deletions prtpy/partitioning/complete_greedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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))

Expand Down Expand Up @@ -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)
Expand Down
20 changes: 12 additions & 8 deletions prtpy/partitioning/integer_programming.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -103,21 +103,25 @@ 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 = {
iitem: [model.add_var(var_type=mip.INTEGER, name=f'item{iitem}_in_bin{ibin}') for ibin in ibins]
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.

Expand Down
24 changes: 9 additions & 15 deletions prtpy/partitioning/integer_programming_avg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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))

0 comments on commit 8f55468

Please sign in to comment.