簡體   English   中英

Python PuLP 優化問題 - 最小化平均偏差

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

我正在嘗試使用 PuLP 進行優化問題,但在編寫目標函數時遇到問題。

我已經將現實生活中的示例簡化為使用谷物的更簡單示例。 因此,假設我有一個產品列表和一些我可以將它們放入的過道(對於此示例 2)。 每個產品都有一個我們通常每周銷售的數量(例如:我們每周銷售 20 盒 Fruit Loops 和 6 盒 Cheerios)。 每個項目還需要一定數量的架子(例如:磨砂片需要 1 個架子,但玉米片需要 2 個)。

產品 銷售量 貨架 指定過道
水果圈 20 2
磨砂片 15 1
可可鵝卵石 8 1
果味鵝卵石 9 1
玉米片 12 2
麥片 6 1

每個過道只有 4 個貨架。 所以理論上我可以把水果圈和玉米片放在一個過道里(2 個架子 + 2 個架子)。 如果我將它們放在過道中,那么該過道的每周銷售額將為 20 + 12 = 32。如果我將其他 4 件商品(1 個貨架 + 1 + 1 + 1)放在過道中,那么該過道的銷售額將為 15 + 8 + 9 + 6 = 38。Aisle 的平均銷售額應該是 35。我優化問題的目標是讓每個 Aisle 盡可能接近平均數。 我想最小化每個通道的每周總銷售額和平均數之間的總絕對差。 在這個例子中,我的偏差是 ABS(38-35) + ABS(32-35) = 6。這是我想要最小化的數字。

我不知道如何寫,所以 PuLP 接受了我的目標。 我無法在網上找到具有這種復雜程度的示例,它將每個值與平均值進行比較並計算累積絕對偏差。 當我寫出技術上可以計算出 PuLP 似乎不接受它的代碼時。

下面是一些示例代碼:

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()

我得到的結果是-3

如果我運行LpStatus[problem.status]我得到Undefined 我認為我的目標函數太復雜了,但我不確定。

任何幫助表示贊賞。

您的主要問題是 ABS 函數是非線性的。 (所以無論你想做什么排序... ;))。 所以你必須重新制定。 這樣做的典型方法是引入一個輔助變量並考慮與 ABS 函數分開的“正”和“負”偏差,因為它們都是線性的。 網站上有幾個這樣的例子,包括我不久前回答的這個例子:

如何在python中使用絕對值進行整數優化?

這引入了將通道選擇帶入索引的需要,因為您需要有通道總和或差異的索引。 這並不太難。

然后您必須(如下所示)設置約束以將新的aisle_diff變量限制為大於 ABS 的正偏差或負偏差。

所以,我認為下面的工作正常。 請注意,我引入了第三個通道,使其更有趣/可測試。 我對你現在死掉的代碼留下了一些評論。

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)

產量:

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