You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Pulp循环添加约束时出现异常结果求助

Pulp循环添加线性约束时数值项异常的解决方法

问题描述

使用Python 3.11.1 + Pulp库构建线性优化模型,循环遍历Pandas DataFrame添加约束时,数值项(Freq、maxPer)未正确解析,生成的约束不符合预期:

  • Freq值未出现在约束左侧
  • maxPer未按预期参与计算,最终约束强制变量取值为0

数据与变量定义

DataFrame(df15)内容

ID                Pos1            Pos2           Freq
28904502    id28904502_PosA                       1
28904503    id28904503_PosB                       2
28904521    id28904521_PosA    id28904521_PosB    1
28904596    id28904596_PosC                       1

已定义的Lp变量

VARIABLES
0 <= id28904502_PosA <= 1 Integer
0 <= id28904503_PosB <= 1 Integer
0 <= id28904521_PosA <= 1 Integer
0 <= id28904521_PosB <= 1 Integer
0 <= id28904596_PosC <= 1 Integer

预期约束规则

每行需生成如下形式的约束(maxPer=2):

limit_ID_28904502: 1 + id28904502_PosA <= 2
limit_ID_28904503: 2 + id28904503_PosB <= 2
limit_ID_28904521: 1 + id28904521_PosA + id28904521_PosB <= 2
limit_ID_28904596: 1 + id28904596_PosC <= 2

现有代码及异常结果

现有代码

maxPer = 2

for n in range(0,df15.shape[0]):
    model1.addConstraint(name=f'limit_ID_{df15.iloc[n,0]}', constraint=lpSum([df15.iloc[n, 3], df15.iloc[n,1], df15.iloc[n, 2]]) <= maxPer)

实际生成的约束

limit_ID_28904502: id28904502_PosA <= 0
limit_ID_28904503: id28904503_PosB <= 0
limit_ID_28904521: id28904521_PosA + id28904521_PosB <= 0
limit_ID_28904596: id28904596_PosC <= 0

补充测试示例

测试代码同样出现数值项丢失、maxPer解析异常的问题:

import pandas as pd
import pulp

maxPer = 2

p1 = pulp.LpVariable('p1')
p2 = pulp.LpVariable('p2')
p3 = pulp.LpVariable('p3')

data = {'ID': [22,45],
        'Pos1': [p1, p2],
        'Pos2': [p3, None],
        'Freq': [2, 1]}

df = pd.DataFrame.from_records(data)[['ID', 'Pos1', 'Pos2', 'Freq']]

for i in range(df.shape[0]):
    expr = pulp.lpSum([df.iloc[i, 1], df.iloc[i, 2], df.iloc[i, 3]] ) <= maxPer
    print(expr)

测试输出

p1 + p3 <= 0
p2 <= 1

问题原因

  1. lpSum参数限制:lpSum仅支持Pulp变量或(系数,变量)对的迭代对象,直接混入普通数值会导致解析异常,数值项可能被错误移项或忽略。
  2. 空值处理不当:None/空字符串被传入lpSum时会被静默忽略,未做过滤。
  3. 表达式自动移项:Pulp会自动将常量项移到不等式右侧,导致约束显示形式与预期不符(数学上等价,但直观性差)。

解决代码

方案1:保留预期显示形式(常量在左侧)

使用LpAffineExpression构建包含常量项的表达式,确保约束显示与预期一致:

import pandas as pd
import pulp

maxPer = 2

# 遍历DataFrame,推荐使用iterrows()更直观
for idx, row in df15.iterrows():
    # 初始化表达式,加入Freq常量
    constraint_expr = pulp.LpAffineExpression(row['Freq'])
    
    # 添加Pos1变量:若df中存的是变量名称字符串,需从模型中获取实际变量
    if pd.notna(row['Pos1']):
        if isinstance(row['Pos1'], str):
            var = model1.variablesDict()[row['Pos1']]
        else:
            var = row['Pos1']
        constraint_expr += var
    
    # 添加Pos2变量
    if pd.notna(row['Pos2']):
        if isinstance(row['Pos2'], str):
            var = model1.variablesDict()[row['Pos2']]
        else:
            var = row['Pos2']
        constraint_expr += var
    
    # 添加约束到模型
    model1.addConstraint(
        name=f'limit_ID_{row["ID"]}',
        constraint=constraint_expr <= maxPer
    )

方案2:数学等价简化(常量移到右侧)

若无需严格保留显示形式,可将Freq移到右侧,代码更简洁:

maxPer = 2

for idx, row in df15.iterrows():
    vars_list = []
    # 收集有效变量
    if pd.notna(row['Pos1']):
        var = model1.variablesDict()[row['Pos1']] if isinstance(row['Pos1'], str) else row['Pos1']
        vars_list.append(var)
    if pd.notna(row['Pos2']):
        var = model1.variablesDict()[row['Pos2']] if isinstance(row['Pos2'], str) else row['Pos2']
        vars_list.append(var)
    
    # 计算右侧阈值
    threshold = maxPer - row['Freq']
    # 添加约束
    model1.addConstraint(
        name=f'limit_ID_{row["ID"]}',
        constraint=pulp.lpSum(vars_list) <= threshold
    )

验证效果

使用方案1处理补充测试示例,输出将与预期一致:

2 + p1 + p3 <= 2
1 + p2 <= 2

内容的提问来源于stack exchange,提问作者Dan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 18:45:53