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

Python PULP - Positional Constraints for a Lineup Generator

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

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

生成器版本 1

例如,球员 A 和球员 B 都被选中的阵容,但他们都是双位置球员,并且他们共享相同的双位置(例如,他们都可以是 PG 或 SG) ,因为生成器将交换它们,因为在 Lineup 1 中,球员 A 被选为 PG,B 被选为 SG,而在 Lineup 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(如果玩家没有第二位置,则可以是 none)。我在这里遇到的问题 运行 是我现在可以生成满足以下示例中的约束的阵容,但阵容在技术上是不正确的。首先我会提供我的新约束,然后我会解释我的问题。

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

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

Player Pos 1 Pos 2
A PG None
B PG None
C PG SG
D PG SG
E SG SF
F SF PF
G SF PF
H C None
I PG SG

根据以上,可用 PG - 5、SG - 4、SF - 3、PF - 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', 'FWD'},
                        '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)