简体   繁体   English

Python PuLP 优化问题 - 最小化平均偏差

[英]Python PuLP Optimization Problem - Minimize Deviation from Average

I am trying to do an Optimization problem with PuLP but I am having an issue with writing my objective function.我正在尝试使用 PuLP 进行优化问题,但在编写目标函数时遇到问题。

I've simplified my real-life example to a simpler one using cereal.我已经将现实生活中的示例简化为使用谷物的更简单示例。 So let's say I have a list of products and a number of Aisles I can put them into (for this example 2).因此,假设我有一个产品列表和一些我可以将它们放入的过道(对于此示例 2)。 Each product has a number we normally sell each week (ex: we sell 20 boxes of Fruit Loops and 6 boxes of Cheerios each week).每个产品都有一个我们通常每周销售的数量(例如:我们每周销售 20 盒 Fruit Loops 和 6 盒 Cheerios)。 Each item also needs a certain amount of shelves (ex: Frosted Flakes needs 1 shelf, but Corn Flakes needs 2).每个项目还需要一定数量的架子(例如:磨砂片需要 1 个架子,但玉米片需要 2 个)。

Product产品 Sales销售量 Shelves货架 Assigned Aisle指定过道
Fruit Loops水果圈 20 20 2 2
Frosted Flakes磨砂片 15 15 1 1
Cocoa Pebbles可可鹅卵石 8 8 1 1
Fruitty Pebbles果味鹅卵石 9 9 1 1
Corn Flakes玉米片 12 12 2 2
Cheerios麦片 6 6 1 1

Each aisle only has 4 shelves.每个过道只有 4 个货架。 So in theory I could put Fruit Loops and Corn Flakes together in one Aisle (2 shelves + 2 shelves).所以理论上我可以把水果圈和玉米片放在一个过道里(2 个架子 + 2 个架子)。 If I put those in an Aisle the weekly sales from that Aisle would be 20 + 12 = 32. If I put the other 4 items (1 shelf + 1 + 1 + 1) in an Aisle then that Aisles sales would be 15 + 8 + 9 + 6 = 38. The average sales in an Aisle should be 35. The goal of my optimization problem is for each Aisle to be as close to that Average number as possible.如果我将它们放在过道中,那么该过道的每周销售额将为 20 + 12 = 32。如果我将其他 4 件商品(1 个货架 + 1 + 1 + 1)放在过道中,那么该过道的销售额将为 15 + 8 + 9 + 6 = 38。Aisle 的平均销售额应该是 35。我优化问题的目标是让每个 Aisle 尽可能接近平均数。 I want to minimize the total absolute difference between each Aisles Total Weekly Sales and the Average number.我想最小化每个通道的每周总销售额和平均数之间的总绝对差。 In this example my deviation would be ABS(38-35) + ABS(32-35) = 6. And this is the number I want to minimize.在这个例子中,我的偏差是 ABS(38-35) + ABS(32-35) = 6。这是我想要最小化的数字。

I cannot figure out the way to write that so PuLP accepts my objective.我不知道如何写,所以 PuLP 接受了我的目标。 I can't find an example online with that level of complexity where it's comparing each value to an average and taking the cumulative absolute deviation.我无法在网上找到具有这种复杂程度的示例,它将每个值与平均值进行比较并计算累积绝对偏差。 And when I write out code that technically would calculate that PuLP doesn't seem to accept it.当我写出技术上可以计算出 PuLP 似乎不接受它的代码时。

Here's some example code:下面是一些示例代码:

products = ['Fruit Loops', 'Frosted Flakes', 'Cocoa Pebbles', 'Fruitty Pebbles', 'Corn Flakes', 'Cheerios']

sales = {'Fruit Loops': 20, 'Frosted Flakes': 15, 'Cocoa Pebbles': 8, 'Fruitty Pebbles': 9, 'Corn Flakes': 12, 'Cheerios': 6}

shelves = {'Fruit Loops': 2, 'Frosted Flakes': 1, 'Cocoa Pebbles': 1, 'Fruitty Pebbles': 1, 'Corn Flakes': 2, 'Cheerios': 1}

from pulp import *

problem = LpProblem('AisleOptimization', LpMinimize)

# For this simplified example there are only 2 aisles
Num_of_Aisles = 2

# Each Aisle has 4 shelves and can't have more than 4 shelves filled
Max_Shelves_Aisle = 4

# The Optimizer can change the Aisle each Product is assigned to try and solve the problem
AislePick = LpVariable.dicts('AislePick', products, lowBound = 0, upBound = (Num_of_Aisles - 1), cat='Integer')

# My attempt at the Minimization Formula
# First Calculate what the average sales would be if split perfectly between aisles
avgsales = sum(sales.values()) / Num_of_Aisles

# Loop through and calculate total sales in each aisle and then subtract from the average
problem += lpSum([sum(v for _, v in value) - avgsales for _, value in itertools.groupby(sorted([(aisle, sales[product]) for product, aisle in AislePick.items()]), lambda x: x[0])]), 'Hits Diff'

# Restriction so each Aisle can only have 4 shelves
for aisle in range(Num_of_Aisles):
    problem += lpSum([shelves[prod] for prod, ais in AislePick.items() if ais == aisle]) <= Max_Shelves_Aisle, f"Sum_of_Slots_in_Aislel{aisle}"

problem.solve()

The result I get is -3我得到的结果是-3

If I run LpStatus[problem.status] I get Undefined .如果我运行LpStatus[problem.status]我得到Undefined I assume my is that my objective function is too complex, but I'm not sure.我认为我的目标函数太复杂了,但我不确定。

Any help is appreciated.任何帮助表示赞赏。

Your main issue here is that the ABS function is non-linear.您的主要问题是 ABS 函数是非线性的。 (So is whatever sorting thing you were trying to do... ;) ). (所以无论你想做什么排序... ;))。 So you have to reformulate.所以你必须重新制定。 The typical way to do this is to introduce a helper variable and consider the "positive" and "negative" deviations separately from the ABS function as both of those are linear.这样做的典型方法是引入一个辅助变量并考虑与 ABS 函数分开的“正”和“负”偏差,因为它们都是线性的。 There are several examples of this on the site, including this one that I answered a while back:网站上有几个这样的例子,包括我不久前回答的这个例子:

How to make integer optimization with absolute values in python? 如何在python中使用绝对值进行整数优化?

That introduces the need bring the aisle selection into the index, because you will need to have an index for the aisle sums or diffs.这引入了将通道选择带入索引的需要,因为您需要有通道总和或差异的索引。 That is not too hard.这并不太难。

Then you have to (as I show below) put in constraints to constrain the new aisle_diff variable to be larger than both the positive or negative deviation from the ABS.然后您必须(如下所示)设置约束以将新的aisle_diff变量限制为大于 ABS 的正偏差或负偏差。

So, I think the below works fine.所以,我认为下面的工作正常。 Note that I introduced a 3rd aisle to make it more interesting/testable.请注意,我引入了第三个通道,使其更有趣/可测试。 And I left a few comments on your now dead code.我对你现在死掉的代码留下了一些评论。

from pulp import *

products = ['Fruit Loops', 'Frosted Flakes', 'Cocoa Pebbles', 'Fruitty Pebbles', 'Corn Flakes', 'Cheerios']
sales = {'Fruit Loops': 20, 'Frosted Flakes': 15, 'Cocoa Pebbles': 8, 'Fruitty Pebbles': 9, 'Corn Flakes': 12, 'Cheerios': 6}
shelves = {'Fruit Loops': 2, 'Frosted Flakes': 1, 'Cocoa Pebbles': 1, 'Fruitty Pebbles': 1, 'Corn Flakes': 2, 'Cheerios': 1}

problem = LpProblem('AisleOptimization', LpMinimize)

# Updated to 3 aisles for testing...
Num_of_Aisles = 3
aisles = range(Num_of_Aisles)

# Each Aisle has 4 shelves and can't have more than 4 shelves filled
Max_Shelves_Aisle = 4

avgsales = sum(sales.values()) / Num_of_Aisles

# The Optimizer can change the Aisle each Product is assigned to try and solve the problem
# value of 1:  assign to this aisle
AislePick = LpVariable.dicts('AislePick', indexs=[(p,a) for p in products for a in aisles], cat='Binary')

#print(AislePick)

# variable to hold the abs diff of aisles sales value...
aisle_diff = LpVariable.dicts('aisle_diff', indexs=aisles, cat='Real')

# constraint:  Limit aisle-shelf capacity
for aisle in aisles:
    problem += lpSum(shelves[product]*AislePick[product, aisle] for product in products) <= Max_Shelves_Aisle

# constraint:  All producst must be assigned to exactly 1 aisle (or the model would make no assignments at all...
#              or possibly make multiple assignements to balance out)
for product in products:
    problem += lpSum(AislePick[product, aisle] for aisle in aisles) == 1

# constraint:  the "positive" aisle difference side of the ABS
for aisle in aisles:
    problem += aisle_diff[aisle] >= \
               lpSum(sales[product]*AislePick[product, aisle] for product in products) - avgsales

# constraint:  the "negative" aisle diff...
for aisle in aisles:
    problem += aisle_diff[aisle] >= \
               avgsales - lpSum(sales[product]*AislePick[product, aisle] for product in products)

# OBJ:  minimize the total diff (same as min avg diff)
problem += lpSum(aisle_diff[aisle] for aisle in aisles)

# My attempt at the Minimization Formula
# First Calculate what the average sales would be if split perfectly between aisles


# Loop through and calculate total sales in each aisle and then subtract from the average
# illegal:  problem += lpSum([sum(v for _, v in value) - avgsales for _, value in itertools.groupby(sorted([(aisle, sales[product]) for product, aisle in AislePick.items()]), lambda x: x[0])]), 'Hits Diff'

# Restriction so each Aisle can only have 4 shelves
# illegal.  You cannot use a conditional "if" statement to test the value of a variable.
#           This statement needs to produce a constraint expression independent of the value of the variable...
# for aisle in range(Num_of_Aisles):
#     problem += lpSum([shelves[prod] for prod, ais in AislePick.items() if ais == aisle]) <= Max_Shelves_Aisle, f"Sum_of_Slots_in_Aislel{aisle}"

problem.solve()

for (p,a) in [(p,a) for p in products for a in aisles]:
    if AislePick[p,a].varValue:
        print(f'put the {p} on aisle {a}')

for a in aisles:
    aisle_sum = sum(sales[p]*AislePick[p,a].varValue for p in products)
    print(f'expectes sales in aisle {a} are ${aisle_sum : 0.2f}')

# for v in problem.variables():
#     print(v.name,'=',v.varValue)

Yields:产量:

Result - Optimal solution found

Objective value:                5.33333333
Enumerated nodes:               22
Total iterations:               1489
Time (CPU seconds):             0.10
Time (Wallclock seconds):       0.11

Option for printingOptions changed from normal to all
Total time (CPU seconds):       0.10   (Wallclock seconds):       0.11

put the Fruit Loops on aisle 0
put the Frosted Flakes on aisle 2
put the Cocoa Pebbles on aisle 2
put the Fruitty Pebbles on aisle 1
put the Corn Flakes on aisle 1
put the Cheerios on aisle 0
expectes sales in aisle 0 are $ 26.00
expectes sales in aisle 1 are $ 21.00
expectes sales in aisle 2 are $ 23.00
[Finished in 281ms]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM