如何将 OR 条件纳入纸浆优化问题?

How to incorporate OR condition into pulp optimization problem?

我正在尝试建立一支梦幻球队,其目标是根据受某些限制的个别球员的预期得分最大化其总预期得分。
这是我的代码的相关部分:

players = [player for player in input_dict]
prices = {player: input_dict[player]['price'] for player in players}
player_teams = {player: input_dict[player]['team'] for player in players}
player_positions = {player: input_dict[player]['position'] for player in players}
points = {player: input_dict[player]['expected_points'] for player in players}
league_teams = list(set(team for team in player_teams.values()))

prob = LpProblem('Fantasy_team', LpMaximize)
player_var = LpVariable.dicts('Players', players, 0, 1, cat='Integer')

# maximize points
prob += lpSum([player_var[player] * points[player] for player in players])
# number of players to be selected
prob += lpSum([player_var[player] for player in players]) == 11 
# max budget
prob += lpSum([player_var[player] * prices[player] for player in players]) <= 100
# position constraints
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'goalkeeper']) == 1
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'defender']) >= 2
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'defender']) <= 5
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'midfielder']) >= 2
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'midfielder']) <= 5
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'forward']) >= 0
prob += lpSum([player_var[player] for player in players if player_positions[player] == 'forward']) <= 3

# max players from one team

for league_team in league_teams:
    team_players = []
    for player, lp_var in player_var.items():
        if player_teams[player] == league_team:
            team_players.append(lp_var)
    prob += lpSum(team_players) <= 3

但是,如果我想添加一个额外的限制条件怎么办:来自一支球队的最少后卫(或任何其他位置)= 2。换句话说,至少有两名后卫必须来自同一支球队。

在我看来,还需要另一个决策变量,但我不明白该怎么做。

编辑:

我取得了一些进展,但我现在遇到了这个错误。显然 Pulp 对 max 函数不满意。有什么办法可以解决这个问题吗?

positions = ['defender',]
team_hires = LpVariable.dicts('count', (positions, league_teams), cat='Integer')

for league_team in league_teams:
    player_vars = []
    for player in player_var:
        if player_teams[player] == league_team:
            player_vars.append(player_var[player])
    team_hires['defender'].update({league_team: lpSum(player_vars)})

prob += max([team_hires['defender'][team] for team in league_teams]) >= 2

TypeError: '>' not supported between instances of 'LpAffineExpression' and 'LpAffineExpression'

我需要重新post这个答案,因为我之前的答案(已删除)令人困惑,我想我误导了你...

为了编写强制至少 1 个团队支持每个职位的最少雇用人数的约束,您需要能够迭代这些团队(因此您需要一组团队)并且您需要能够迭代具有最低雇用限制的职位。下面的示例使用“多项选择”约束来做到这一点,该约束是一个二元变量,这意味着源团队正在支持特定职位的最低雇用人数。因此,如果求解器“选择”了一个特定的团队来支持最低雇用人数,则该变量为“1”,那么该团队的雇用人数必须大于最低要求(即约束的第一部分) .第二部分是您必须强制所有团队的该变量总和至少为 1,以确保至少有一个团队被迫支持该要求。这是约束的第二部分。

请注意,如果您将决策变量更改为三重索引 [player, pos, source_team],语法可能会更清晰一些,但这仍然可以正常工作!

数据文件(data.csv)

player,price,team,position,expected_points
bob,24,wolves,defender,2.1
sam,23,wolves,defender,2.4
tommy,20,wolves,forward,3.4
bill,20,wolves,forward,3.6
eddie,22,wolves,forward,3.1
tim,23,bears,defender,2.2
earl,23,bears,defender,1.0
dorf,24,bears,forward,3.5
bennie,30,bears,forward,3.6

型号

from pulp import *
import pandas as pd

df = pd.read_csv('data.csv')
df = df.set_index('player')
input_dict = df.to_dict('index')


players = [player for player in input_dict]
prices = {player: input_dict[player]['price'] for player in players}
player_teams = {player: input_dict[player]['team'] for player in players}
player_positions = {player: input_dict[player]['position'] for player in players}
points = {player: input_dict[player]['expected_points'] for player in players}
league_teams = list(set(team for team in player_teams.values()))

# min numbers per position
pos_mins = {'defender':2,
            'forward':2}

# min numbers from single team by position
team_pos_mins = {'defender':2}   # must hire at least 2 defenders from same source team
positions = player_positions.values()
pos_team_combos = {(pos, team) for pos in positions for team in league_teams}

prob = LpProblem('Fantasy_team', LpMaximize)
hire = LpVariable.dicts('Players', players, cat='Binary')   # this is binary decision...
support_min_hires = LpVariable.dicts('team hires', pos_team_combos, cat='Binary')  # 1 if that team supports the min for position


# maximize points
prob += lpSum([hire[player] * points[player] for player in players])
# number of players to be selected
prob += lpSum([hire[player] for player in players]) == 4
# max budget
prob += lpSum([hire[player] * prices[player] for player in players]) <= 100
# position constraints
for pos in pos_mins:
    # this pattern could be replicated for all of your max/mins
    prob += lpSum(hire[player] for player in players if player_positions[player] == pos) >= pos_mins[pos]

# hires by team constraint
for pos in team_pos_mins:
    # the min number hired from team must be greater than the requirement, if that team is selected to support the min...
    for team in league_teams:
        prob += lpSum(hire[player] for player in players 
                if player_positions[player] == pos
                and player_teams[player] == team) >= support_min_hires[pos, team] * team_pos_mins[pos]
    # force at least one team to suppoprt the minimum hires
    prob += lpSum(support_min_hires[pos,team] for team in league_teams) >= 1

#print(prob)

status = prob.solve()
print(status)
for p in players:
    print(p, hire[p].value())

# for QA...
for pos in team_pos_mins:
    for team in league_teams:
        print(f'{team} covers min hires for {pos}:  {support_min_hires[pos,team].value()>0}')

结果

Result - Optimal solution found

Objective value:                11.70000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.00
Time (Wallclock seconds):       0.00

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

1
bob 1.0
sam 1.0
tommy 0.0
bill 1.0
eddie 0.0
tim 0.0
earl 0.0
dorf 0.0
bennie 1.0
wolves covers min hires for defender:  True
bears covers min hires for defender:  False
[Finished in 0.9s]