在 PuLP Objective 函数中实现二元约束

Implementing binary constraint in PuLP Objective Function

我正在尝试使用线性规划确定连接到电网的电池可以获得的最大收入。电池可以在能源市场和频率市场两个市场赚取收入。当我在 objective 函数中包含二元约束时,我的模型抛出错误(TypeError:非常量表达式不能相乘)。

我的Objective函数是:

电池在每个时间段 t 只能在一个市场(能量或频率)中处于活动状态。所以需要一个看起来像这样的约束:

其中 是激活 activity x 的二进制变量。

好的,这就是我想要实现的目标。我正在努力在纸浆中创造这样一种限制,如果另一个市场的价值更高(满足所有其他限制),则基本上会关闭对一个市场的参与。在我的电池 class 中,我为每个电源活动及其 on/off 状态创建了决策变量。


self.charge = \
pulp.LpVariable.dicts(
    "charging_power",
    ('c_t_' + str(i) for i in range(0,time_horizon)),
    lowBound=0, upBound=max_charge_power_capacity,
    cat='Continuous')

self.discharge = \
pulp.LpVariable.dicts(
    "discharging_power",
    ('d_t_' + str(i) for i in range(0,time_horizon)),
    lowBound=0, upBound=max_discharge_power_capacity,
    cat='Continuous')

self.freq = \
pulp.LpVariable.dicts(
    "freq_power",
    ('f_t_' + str(i) for i in range(0,time_horizon)),
    lowBound=0, upBound=max_freq_power_capacity,
    cat='Continuous')

self.charge_status = \
pulp.LpVariable.dicts(
    "charge_status",
    ('c_status_t_' + str(i) for i in range(0,time_horizon)),
    cat='Binary')

self.discharge_status = \
pulp.LpVariable.dicts(
    "discharge_status",
    ('d_status_t_' + str(i) for i in range(0,time_horizon)),
    cat='Binary')

self.freq_status = \
pulp.LpVariable.dicts(
    "freq_status",
    ('ds3_status_t_' + str(i) for i in range(0,time_horizon)),
    cat='Binary')

在我的 objective 函数中,我包含了这些二进制变量。

    
self.model = pulp.LpProblem("Max Profit", pulp.LpMaximize)

self.model +=\
pulp.lpSum(
    [self.charge['c_t_' + str(i)]*-1*prices[i] *
     self.charge_status['c_status_t_' + str(i)] for i in range(0,self.time_horizon)]
    + [self.discharge['d_t_' + str(i)]*prices[i] * 
     self.discharge_status['d_status_t_' + str(i)] for i in range(0,self.time_horizon)]
    + [self.freq['f_t_' + str(i)]*freq_prices[i] *
     self.freq_status['freq_status_t_' + str(i)] for i in range(0,self.time_horizon)]
)

对于这些二进制变量的约束,我设置如下:


 for hour_of_sim in range(1,self.time_horizon+1):     
    self.model += \
        pulp.lpSum([self.charge_status['c_status_t_' + str(i)] for i in range(0,self.time_horizon)] +\
                   [self.discharge_status['d_status_t_' + str(i)] for i in range(0,self.time_horizon)] +\
                   [self.freq_status['freq_status_t_' + str(i)] for i in range(0,self.time_horizon)]
                  ) <= 1

当我尝试求解时,我得到一个

TypeError: Non-constant expressions cannot be multiplied

在 objective 函数上。不喜欢我的二进制变量,如果它们被删除则运行。一定有另一种设置方法让我逃避了?

评论是正确的...您将 2 个变量相乘违反了“线性”。幸运的是,这很容易线性化。您有一个控制模式的二进制变量,因此您正在寻找的关键元素(google 它)是一个 Big-M 约束,您可以在其中使用二进制变量乘以最大值(或足够的值)大)将另一个变量限制为最大值,或将其限制为零。

下面是一个例子。我也re-arranged有点东西。您可能会发现这种风格更具可读性。风格的两个主要方面:

  1. 你经常re-creating你正在使用的索引确实很难阅读并且容易出错。只需制作它们并 re-use 它们...您不需要对索引设置值感到复杂

  2. 你可以轻松double-index这个模型,我觉得比做多组变量更清晰。您基本上有 2 组正在使用:时间段和操作模式。只需制作这些集合和双索引即可。

例子

# battery modes

import pulp

# some data
num_periods = 3
rate_limits = { 'energy'    : 10,
                'freq'      : 20}
price = 2  # this could be a table or double-indexed table of [t, m] or ....

# SETS
M = rate_limits.keys()   # modes.  probably others...  discharge?
T = range(num_periods)   # the time periods

TM = {(t, m) for t in T for m in M}

model = pulp.LpProblem('Batts', pulp.LpMaximize)

# VARS
model.batt = pulp.LpVariable.dicts('batt_state', indexs=TM, lowBound=0, cat='Continuous')
model.op_mode = pulp.LpVariable.dicts('op_mode', indexs=TM, cat='Binary')

# Constraints

# only one op mode in each time period...
for t in T:
    model += sum(model.op_mode[t, m] for m in M) <= 1

# Big-M constraint. limit rates for each rate, in each period.
# this does 2 things:  it is equivalent to the upper bound parameter in the var declaration
#                      It is a Big-M type of constraint which uses the binary var as a control <-- key point
for t, m in TM:
    model += model.batt[t, m] <= rate_limits[m] * model.op_mode[t, m]

# OBJ
model += sum(model.batt[t, m] * price for t, m in TM)

print(model)

#  solve...

产量:

Batts:
MAXIMIZE
2*batt_state_(0,_'energy') + 2*batt_state_(0,_'freq') + 2*batt_state_(1,_'energy') + 2*batt_state_(1,_'freq') + 2*batt_state_(2,_'energy') + 2*batt_state_(2,_'freq') + 0
SUBJECT TO
_C1: op_mode_(0,_'energy') + op_mode_(0,_'freq') <= 1

_C2: op_mode_(1,_'energy') + op_mode_(1,_'freq') <= 1

_C3: op_mode_(2,_'energy') + op_mode_(2,_'freq') <= 1

_C4: batt_state_(2,_'freq') - 20 op_mode_(2,_'freq') <= 0

_C5: batt_state_(2,_'energy') - 10 op_mode_(2,_'energy') <= 0

_C6: batt_state_(1,_'freq') - 20 op_mode_(1,_'freq') <= 0

_C7: batt_state_(1,_'energy') - 10 op_mode_(1,_'energy') <= 0

_C8: batt_state_(0,_'freq') - 20 op_mode_(0,_'freq') <= 0

_C9: batt_state_(0,_'energy') - 10 op_mode_(0,_'energy') <= 0

VARIABLES
batt_state_(0,_'energy') Continuous
batt_state_(0,_'freq') Continuous
batt_state_(1,_'energy') Continuous
batt_state_(1,_'freq') Continuous
batt_state_(2,_'energy') Continuous
batt_state_(2,_'freq') Continuous
0 <= op_mode_(0,_'energy') <= 1 Integer
0 <= op_mode_(0,_'freq') <= 1 Integer
0 <= op_mode_(1,_'energy') <= 1 Integer
0 <= op_mode_(1,_'freq') <= 1 Integer
0 <= op_mode_(2,_'energy') <= 1 Integer
0 <= op_mode_(2,_'freq') <= 1 Integer