当详尽搜索的时间过长时,如何找到最佳组合? 数学模型 Python代码讨论解决方案

问题描述

免责声明:我是一个完全的业余爱好者,没有经过正式的培训,只是教了一些C#和Python

有47个插槽。每个项目都必须填写一项。 这些插槽分为8组。 项目归为相同的8组。
每个插槽只能由同一组中的一个项目填充。
同一项目可用于填充多个插槽。

每个项目都包含一个名称一个组和9个统计信息。

item ("name","group","stat1","stat2","stat3",...,"stat9")

exampleItem (exampleName,groupA,3,8,19,431)

每个插槽都包含一个ID和一个组。

slot1 (1,groupC)

每个插槽中都装有一个物品(遵循上述规则)。

然后将每个不同的统计信息相加。

stat1Total=item1(stat1)+item2(stat1)+item3(stat1)+...+item47(stat1)

目标是:
-每个插槽都填有相应组的项目。 (没有空插槽)
-具有stat1Total,stat2Total和stat3Total达到一定量(stat1Goal,stat2Goal,stat3Goal)
-让stat1Total,stat2Total和stat3Total越少越少
-最大化每个其他statTotal的权重

输出满足上述目标的最佳项目组合。 (如果2个或更多组合同样是最佳组合,请全部输出

我试图仅搜索所有可能的组合以获得最佳输出,但是总的来说,这总计为2.16x10 ^ 16种可能的组合,所以我认为这不是可行的。
现在我不知道如何解决这个问题,而且我对整数编程的了解越多,我就越困惑。

为说明这个问题,我将举一个简化的示例,其中包含3个插槽,2个组和5个项目:

SlotA,SlotB和SlotC必须分别填充5个项目之一。
插槽A和插槽B属于组Group1,而插槽C属于组2。
这些项目分为相同的组。因此,我们有属于组1的Item1,Item2和Item3,以及属于组2的Item4和Item5。

这意味着SlotA和SlotB只能由Item1,Item2和Item3填充。您可以想象插槽是不同形状的孔,而形状适合这些孔的项目。您不能将星形钉插入方孔中,也不能将Item5插入SlotB中,因为它不合适。

商品本身具有不同的统计信息。在此示例中,我们假设每个项目仅具有其所属的组和2个统计信息:StatDark,StatLight。这些可以取0到10之间的整数值。

Item1(Group1,StatDark=3,StatLight=5)
Item2(Group1,StatDark=7,StatLight=1)
Item3(Group1,StatDark=2,StatLight=5)
Item4(Group2,StatDark=1,StatLight=6)
Item5(Group2,StatLight=5)

此示例的目标是:
-在遵守分组规则的同时在每个广告位中填充一个项目
-使所有选定项目中所有StatDark的总和等于或大于9
-最小化StatDark高于9(每个StatDark高于9都是无用和浪费的)
-最大化所有选定项目中所有StatLight的总和

在这种情况下,最佳解决方案是:
广告位A和广告位B:项目2和项目3
SlotC:Item4

以下是此示例的完整表链接https://imgur.com/MH80G3O

我希望这个例子可以使问题更容易理解。

解决方法

数学模型

我要介绍一个二进制变量:

x[i,j] = 1 if item i is assigned to slot j
         0 otherwise

这是我们的第一个问题:不允许使用许多(i,j)组合。智能模型将尝试不生成禁止的组合。因此,我们需要实现一个稀疏变量,而不是一个完全分配的变量。仅当x[i,j]为真时,我们才希望分配和使用allowed(i,j)

此外,引入一个连续变量xstat[s]来计算每个统计信息的总值。

有了这个,我们就可以写下约束:

  sum( i | allowed(i,j),x[i,j] ) = 1  for all slots j          (exactly one item in each slot)
  xstat[s] = sum( (i,j) | allowed(i,j] * stat[i,s])     (calculate total stats)  
  xstat['StatDark'] >= 9                                         

目标是两个目标的加权总和:

  minimize xstat['StatDark'] - 9
  maximize xstat['StatLight']

所以我们这样做:

  maximize -w1*(xstat['StatDark'] - 9) +  w2*xstat['StatLight']

用于用户提供的权重w1和w2。

这两个问题使问题变得更加复杂。此外,我们需要对数据做一些工作,以使其适合用于优化模型。

Python代码

import pandas as pd
import pulp as lp
from io import StringIO

#----------------------------------------
# raw data
#----------------------------------------

itemData = pd.read_table(StringIO("""
Item   Group  StatDark StatLight
Item1  Group1    3        5
Item2  Group1    7        1
Item3  Group1    2        5
Item4  Group2    1        6
Item5  Group2    2        5
"""),sep="\s+")

slotData = pd.read_table(StringIO("""
Slot   Group
SlotA  Group1
SlotB  Group1
SlotC  Group2
"""),sep='\s+')

# minimum total of Dark
minDark = 9

# stats
stat = ['StatDark','StatLight']

# objective weights
# we have two objectives and there are trde offs between them.
# here we use a simple weighted sum approach. These are the weights.
w = {'StatDark':0.3,'StatLight':0.7}

#----------------------------------------
# data prep
#----------------------------------------


# join on Group
# we need to know which (Item,Slot) combinations are allowed.
merged = pd.merge(itemData[['Item','Group']],slotData,on='Group').set_index(['Item','Slot'])
 
items = itemData['Item']
slots = slotData['Slot']


# we will use the convention:
#  i : items
#  j : slots
#  s : stats

# stat values
# easy lookup of statv[(i,s)]
itemData = itemData.set_index('Item')
statv = {(i,s):itemData.loc[i,s] for i in items for s in stat}

#----------------------------------------
# MIP model
#----------------------------------------

# x[(i,j)] = 1 if item i is assigned to slot j
#            0 otherwise
# only use combinations (i,j) that are allowed
x = lp.LpVariable.dicts('x',[(i,j) for i,j in merged.index],cat='Binary') 

# xstat[stat] = total accumulated values
xstat = lp.LpVariable.dicts('xstat',[s for s in stat],cat='Continuous',lowBound = 0)

prob = lp.LpProblem("assignItemsToSlots",lp.LpMaximize)

# objective: weighted sum
#----------
# 1. minimize statDark exceeding minDark
# 2. maximize statLight
prob +=  -w['StatDark']*(xstat['StatDark'] - minDark) + w['StatLight']*xstat['StatLight']

# constraints
# -----------

# each slot much have exactly one item
# (but the same item can go to different slots)
for j in slots:
  prob += lp.lpSum([x[(i,j)] for i,jj in merged.index if jj==j]) == 1

#  minimum total value for dark 
prob += xstat['StatDark'] >= minDark 

# calculate stat totals
for s in stat:
  prob += xstat[s] == lp.lpSum([x[(i,j)]*statv[i,s] for i,j in merged.index])


#----------------------------------------
# solve problem
#----------------------------------------

prob.solve()
# to show the log use 
# solve(pulp.PULP_CBC_CMD(msg=1))

print("Status:",lp.LpStatus[prob.status])
print("Objective:",lp.value(prob.objective))

#----------------------------------------
# solution
#----------------------------------------

assigned = []
for i,j in merged.index:
  if lp.value(x[(i,j)]) > 0.5:
    assigned += [[i,j]]
assigned = pd.DataFrame(assigned,columns=['item','slot'])
print(assigned)

讨论

merged如下:

Item   Slot   Group
-----------------------
Item1   SlotA   Group1
        SlotB   Group1
Item2   SlotA   Group1
        SlotB   Group1
Item3   SlotA   Group1
        SlotB   Group1
Item4   SlotC   Group2
Item5   SlotC   Group2 

Item,Slot列为我们提供了允许的组合。

字典statv提供了方便的数据结构来访问统计信息:

{('Item1','StatDark'): 3,('Item1','StatLight'): 5,('Item2','StatDark'): 7,'StatLight'): 1,('Item3','StatDark'): 2,('Item4','StatDark'): 1,'StatLight'): 6,('Item5','StatLight'): 5}

生成的MIP模型如下:

assignItemsToSlots:
MAXIMIZE
-0.3*xstat_StatDark + 0.7*xstat_StatLight + 2.6999999999999997
SUBJECT TO
_C1: x_('Item1',_'SlotA') + x_('Item2',_'SlotA') + x_('Item3',_'SlotA') = 1

_C2: x_('Item1',_'SlotB') + x_('Item2',_'SlotB') + x_('Item3',_'SlotB') = 1

_C3: x_('Item4',_'SlotC') + x_('Item5',_'SlotC') = 1

_C4: xstat_StatDark >= 9

_C5: - 3 x_('Item1',_'SlotA') - 3 x_('Item1',_'SlotB')
 - 7 x_('Item2',_'SlotA') - 7 x_('Item2',_'SlotB') - 2 x_('Item3',_'SlotA')
 - 2 x_('Item3',_'SlotB') - x_('Item4',_'SlotC') - 2 x_('Item5',_'SlotC')
 + xstat_StatDark = 0

_C6: - 5 x_('Item1',_'SlotA') - 5 x_('Item1',_'SlotB') - x_('Item2',_'SlotA')
 - x_('Item2',_'SlotB') - 5 x_('Item3',_'SlotA') - 5 x_('Item3',_'SlotB')
 - 6 x_('Item4',_'SlotC') - 5 x_('Item5',_'SlotC') + xstat_StatLight = 0

VARIABLES
0 <= x_('Item1',_'SlotA') <= 1 Integer
0 <= x_('Item1',_'SlotB') <= 1 Integer
0 <= x_('Item2',_'SlotA') <= 1 Integer
0 <= x_('Item2',_'SlotB') <= 1 Integer
0 <= x_('Item3',_'SlotA') <= 1 Integer
0 <= x_('Item3',_'SlotB') <= 1 Integer
0 <= x_('Item4',_'SlotC') <= 1 Integer
0 <= x_('Item5',_'SlotC') <= 1 Integer
xstat_StatDark Continuous
xstat_StatLight Continuous

解决方案

解决方案如下:

Status: Optimal
Objective: 8.099999999999998
    item   slot
0  Item2  SlotB
1  Item3  SlotA
2  Item4  SlotC