Chris
Chris

Reputation: 565

Minimize AbsEquality rather than enforce in OrTools

I'm trying to solve the following using OR tools:

Given the following bags containing different colors of balls:

bag red blue green black
A 10 5 85 0
B 25 50 25 0
C 0 100 0 0
D 90 5 5 0
E 2 0 98 0
F 0 0 0 100

How many of each type of bag would I need to have an equal number of each color of ball?

For cases like this where there is an exact answer, the following code:

bags= [
[10,5,85,0],
[25,50,25,0],
[0,100,0,0],
[90,5,5,0],
[2,0,98,0],
[0,0,0,100]
]


bags_n = len(bags)
color_n = len(bags[0])

print(f'Bags: {bags_n}')
print(f'Colors: {color_n}')

color_count= [0] * color_n

for c in range(color_n):
  for b in bags:
    color_count[c]+= b[c]


print(color_count)
print(f'Inital total: {sum(color_count)}')
print(f'Inital equal share: {sum(color_count)//color_n}')

model = cp_model.CpModel()

weights = []

for r in range(bags_n):
  weights.append(model.NewIntVar(1,1000,f'Weight of Bag: {r}'))

total = model.NewIntVar(0, 100000, 'total')

model.Add(
    sum(flatten(
          [[bags[r][c] * weights[r] for r in range(bags_n)] for c in range(color_n)]
        )) == total
)
          
equal = model.NewIntVar(0, 10000, 'equal share')
model.AddDivisionEquality(equal, total, color_n)

for c in range(color_n):
  diff_c = model.NewIntVar(0, 1000, 'diff_'+str(c))
  model.Add(diff_c == sum([bags[r][c] * weights[r] for r in range(bags_n)]) - equal)
  model.AddAbsEquality(0, diff_c)

solver = cp_model.CpSolver()
status = solver.Solve(model)

if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
    print(f'Maximum of objective function: {solver.ObjectiveValue()}\n')
    for v in weights:
      print(f'{solver.Value(v)}')
    print(f'total = {solver.Value(total)}')
    print(f'equal share = {solver.Value(equal)}')
else:
    print(status)

gives back valid weights:

82 2 70 78 5 79

If I change the setup to something like

bags= [
[50,40,10],
[30,20,50],
[30,30,40],
[30,25,45],
]

The model becomes infeasible, I assume due to the fact that there are no weights that satisfy the AbsEquality for every color.

How can I change this to get me the solution closest to an even distribution even if a perfect solution is infeasable?

Upvotes: 1

Views: 215

Answers (1)

Chris
Chris

Reputation: 565

Christopher Hamkins' suggestion worked great:

bags= [
[50,40,10],
[30,20,50],
[30,30,40],
[30,25,45],
]


bags_n = len(bags)
color_n = len(bags[0])

print(f'Bags: {bags_n}')
print(f'Colors: {color_n}')

color_count= [0] * color_n

for c in range(color_n):
  for b in bags:
    color_count[c]+= b[c]



    
print(color_count)
print(["{0:.0%}".format(c/sum(color_count)) for c in color_count])


model = cp_model.CpModel()

weights = []

for r in range(bags_n):
  weights.append(model.NewIntVar(1,500,f'Weight of Bag: {r}'))

max = model.NewIntVar(0,100000000,f'Max')
model.AddMaxEquality(max,
                     [sum([bags[r][c] * weights[r] for r in range(bags_n)]) for c in range(color_n)]
                     )

min = model.NewIntVar(0,100000000,f'Min')
model.AddMinEquality(min,
                     [sum([bags[r][c] * weights[r] for r in range(bags_n)]) for c in range(color_n)]
                     )
diff = model.NewIntVar(0,100000000,f'Diff')
model.Add(max - min == diff)

model.Minimize(diff)


solver = cp_model.CpSolver()
status = solver.Solve(model)

if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
    print(f'max = {solver.Value(max)}')
    print(f'min = {solver.Value(min)}')
    print(f'diff = {solver.Value(diff)}')

    bag_weights = [0] * bags_n

    for i,v in enumerate(weights):
      bag_weights[i] = solver.Value(v)
      print(f'{solver.Value(v)}')
    
    color_count = [0] * color_n

    for c in range(color_n):
      for i,b in enumerate(bags):
        color_count[c]+= (b[c] * bag_weights[i])


    
    print(color_count)
    print(["{0:.0%}".format(c/sum(color_count)) for c in color_count])

else:
    print(status)

Upvotes: 1

Related Questions