Python PULP - 阵容生成器的位置约束

问题描述

我构建了一个 NBA 阵容生成器,可以优化 9 个位置的阵容(2 个控球后卫 (PG)、2 ​​个得分后卫 (SG)、2 ​​个小前锋 (SF)、2 个大前锋 (PF),1 x 中心 (C)) 使用为每个玩家提供的投影。

最初为了处理可以打两个位置的球员,我设置了一个限制,阻止同一球员在一个阵容中被选中两次,并且它工作正常。我遇到的问题是偶尔当我建立多个阵容时,其中两个会是重复的,但程序认为它们是不同的。

生成器版本 1

例如发生的情况是建立了一个阵容,其中球员 A 和球员 B 都被选中,但他们都是双位置球员,并且共享相同的双位置(例如,两者都可以是 PG 或 SG),例如生成器将交换它们,在阵容 1 中,A 选择为 PG,B 为 SG,而在阵容 2 中,A 选择为 SG,B 为 PG。

我在第一个版本中限制位置的代码如下:

    prob += (pulp.lpSum(self.positions['PG'][i] * players_lineup[i] for i in range(self.num_players)) == 2)
    prob += (pulp.lpSum(self.positions['SG'][i] * players_lineup[i] for i in range(self.num_players)) == 2)
    prob += (pulp.lpSum(self.positions['SF'][i] * players_lineup[i] for i in range(self.num_players)) == 2)
    prob += (pulp.lpSum(self.positions['PF'][i] * players_lineup[i] for i in range(self.num_players)) == 2)
    prob += (pulp.lpSum(self.positions['C'][i] * players_lineup[i] for i in range(self.num_players)) == 1)

因为程序将玩家 A 的 PG 版本和玩家 A 的 SG 版本视为单独的实体,所以它实际上改变了结果(尽管它在功能上是相同的)。

生成器版本 2

所以我创建了一个较新的版本,其中每个玩家都有一个位置 1 和一个位置 2(如果玩家没有第二个位置,则可以没有)。我在这里遇到的问题是我现在可以生成满足以下示例中的约束的阵容,但该阵容在技术上是不正确的。首先我会提供我的新约束,然后我会解释我的问题。

    #Ensures that the lineup has at least 2 potential suitors for PG
    prob += (pulp.lpSum(self.positions['PG'][i] * players_lineup[i] for i in range(self.num_players)) >= 2)
    # Ensures that the lineup has no more than 2 players who can only play PG
    prob += (pulp.lpSum(
        self.positions['PG'][i] * self.dualPosition[i] * players_lineup[i] for i in range(self.num_players)) <=2)
    #Ensures that the lineup has at least 2 potential suitors for SG
    prob += (pulp.lpSum(self.positions['SG'][i] * players_lineup[i] for i in range(self.num_players)) >= 2)
    # Ensures that the lineup has no more than 2 players who can only play SG
    prob += (pulp.lpSum(
        self.positions['SG'][i] * self.dualPosition[i] * players_lineup[i] for i in range(self.num_players)) <= 2)
    #Ensures that the lineup has at least 2 potential suitors for SF
    prob += (pulp.lpSum(self.positions['SF'][i] * players_lineup[i] for i in range(self.num_players)) >= 2)
    # Ensures that the lineup has no more than 2 players who can only play SF
    prob += (pulp.lpSum(
        self.positions['SF'][i] * self.dualPosition[i] * players_lineup[i] for i in range(self.num_players)) <= 2)
    #Ensures that the lineup has at least 2 potential suitors for PF
    prob += (pulp.lpSum(self.positions['PF'][i] * players_lineup[i] for i in range(self.num_players)) >= 2)
    # Ensures that the lineup has no more than 2 players who can only play PF
    prob += (pulp.lpSum(
        self.positions['PF'][i] * self.dualPosition[i] * players_lineup[i] for i in range(self.num_players)) <= 2)
    #Ensures that the lineup has at least 1 potential suitor for C
    prob += (pulp.lpSum(self.positions['C'][i] * players_lineup[i] for i in range(self.num_players)) >= 1)
    # Ensures that the lineup has no more than 1 player who can only play C
    prob += (pulp.lpSum(
        self.positions['C'][i] * self.dualPosition[i] * players_lineup[i] for i in range(self.num_players)) <= 1)

在这里面临的问题是约束条件得到满足,但它们显然没有达到我希望它们实现的目标,哈哈!我发现正在构建如下阵容:

玩家 位置 1 位置 2
A PG
B PG
C PG SG
D PG SG
E SG SF
F SF PF
G SF PF
H C
PG SG

基于以上,可用 PGs - 5、SGs - 4、SFs - 3、PFs - 2、C - 1 的总数都符合我的最低标准。问题是 3 个球员弥补了所有 SF 和 PF 插槽的可用性,因此如果我将 2 个 PF 选项放入 2 个 PF 插槽中,我只剩下 1 个 SF 可用于 2 个 SF 插槽。

我不知道如何更新我的限制以确保所有阵容点都有保证的供应。

对于冗长的文章深表歉意,如果有什么我可以澄清的,请告诉我。

解决方法

我认为如果你重新表述你的问题,你会更快乐。对于这里的整数程序来说,用一组球员和一组位置对它进行双重索引是很自然的。在你的限制条件等中,事情会变得更加清晰,因为你可以对球员或位置进行总结,以更清楚地表述你的问题。确实不清楚您的许多模型项是什么 positions[i]dualPositions[i]。这是一个解决问题的玩具模型,可以帮助您考虑对这个坏男孩进行双重索引...

请注意,有几种方法可以处理其中的“合法分配”部分。下面是如何做到这一点的一个概念。

# hoops dream team

from pulp import *

salaries = {'bob'   : 100,'steve' : 110,'bernie': 105,'eugene': 120,'biff'  : 115,'reggie': 99,'mike'  : 102}

positions = {'PG','FWD','CTR'}

legal_assignments = {   'bob'   : {'PG'},'steve' : {'PG','FWD'},'bernie': {'FWD','CTR'},'eugene': {'FWD','PG'},'biff'  : {'PG'},'reggie': {'CTR','mike'  : {'FWD'}}

legal_assignments_set = { (k,v) for k in legal_assignments.keys() for v in legal_assignments[k]}

prob = LpProblem("Team",LpMinimize)

assign = LpVariable.dicts("assignment",legal_assignments_set,cat="Binary")

#minimize cost of team
prob += sum(assign[player,pos] * salaries[player] for (player,pos) in legal_assignments_set)

# constraints...

# only assign each player once
for player in salaries.keys():
    prob += sum(assign[player,pos] for pos in positions if (player,pos) in legal_assignments_set) <=1

# fill each position to 2 PG,2 FWD,1 CTR
prob += sum(assign[player,'PG'] for player in salaries.keys() if (player,'PG') in legal_assignments_set) == 2
prob += sum(assign[player,'FWD'] for player in salaries.keys() if (player,'FWD') in legal_assignments_set) == 2
prob += sum(assign[player,'CTR'] for player in salaries.keys() if (player,'CTR') in legal_assignments_set) == 1

prob.solve()
print('Status:',LpStatus[prob.status])
for v in prob.variables():
    print(v.name,'=',v.varValue)