如何使用 python PuLP 向优化问题添加顺序(时间序列)约束?

How to add sequential (time series) constraint to optimization problem using python PuLP?

一个简单的优化问题:根据能源成本找到冰箱的最佳控制序列。唯一的限制是保持在温度阈值以下,并且 objective 函数试图最小化所用能源的成本。这个问题被简化了,所以控件只是一个二进制数组,即。 [0, 1, 0, 1, 0],其中1表示用电给冰箱降温,0表示关闭制冷机制(表示这段时间没有成本,但温度会升高)。我们可以假设每个时期都是固定的时间段,并且根据其 on/off 状态具有恒定的温度变化。

以下是示例值:

Cost of energy (for our example 5 periods): [466, 426, 423, 442, 494]
Minimum cooling periods (just as a test): 3
Starting temperature: 0
Temperature threshold(must be less than or equal): 1
Temperature change per period of cooling: -1
Temperature change per period of warming (when control input is 0): 2

下面是 PuLP 中的代码

from pulp import LpProblem, LpMinimize, LpVariable, lpSum, LpStatus, value 
from itertools import accumulate


l = list(range(5))
costy = [466, 426, 423, 442, 494]
cost = dict(zip(l, costy))
min_cooling_periods = 3

prob = LpProblem("Fridge", LpMinimize)
si = LpVariable.dicts("time_step", l, lowBound=0, upBound=1, cat='Integer')
prob += lpSum([cost[i]*si[i] for i in l])                  # cost function to minimize
prob += lpSum([si[i] for i in l]) >= min_cooling_periods   # how many values must be positive
prob.solve()

在我尝试考虑温度阈值之前,优化似乎起作用了。仅使用成本函数,它 returns 一个 0 数组,这确实使成本最小化(duh)。使用第一个约束(多少个值必须为正),它选择最便宜的 3 个冷却期,并正确计算总成本。

obj = value(prob.objective)
print(f'Solution is {LpStatus[prob.status]}\nThe total cost of this regime is: {obj}\n')
for v in prob.variables():
    print(f'{v.name} = {v.varValue}')

output:
Solution is Optimal
The total cost of this regime is: 1291.0

time_step_0 = 0.0
time_step_1 = 1.0
time_step_2 = 1.0
time_step_3 = 1.0
time_step_4 = 0.0

所以,如果我们的控制序列是 [0, 1, 1, 1, 0],那么在每个 cooling/warming 周期结束时,温度将如下所示:[2, 1, 0, - 1, 1]。每当控制输入为 1 时,温度升高 2,当控制输入为 1 时,温度升高 1。这个示例序列是一个有效的答案,但如果我们添加最大温度阈值 1,则必须更改,这意味着第一个值必须是 1,否则冰箱会升温到 2 的温度。

然而,当我尝试指定保持在温度阈值内的顺序约束条件时,我得到了不正确的结果:

up_temp_thresh = 1
down = -1
up = 2
# here is where I try to ensure that the control sequence would never cause the temperature to
# surpass the threshold. In practice I would like a lower and upper threshold but for now 
# let us focus only on the upper threshold.
prob += lpSum([e <= up_temp_thresh for e in accumulate([down if si[i] == 1. else up for i in l])]) >= len(l)

在这种情况下,答案和以前一样,我显然没有正确地表述它,因为序列 [0, 1, 1, 1, 0] 会超过阈值。

我正在尝试编码“每个控制序列末尾的温度必须低于阈值”。我通过将控制序列转换为温度变化数组来实现这一点,因此控制序列 [0, 1, 1, 1, 0] 为我们提供了温度变化 [2, -1, -1, -1, 2]。然后使用 accumulate 函数,它计算一个累积和,等于每一步后的冰箱温度,即 [2, 1, 0, -1, 1]。我只想检查这个数组的最大值是否小于阈值,但是使用 lpSum 我检查数组中小于阈值的值的总和是否等于数组的长度,这应该是同一件事.

但是我显然错误地制定了这个步骤。正如所写,这最后一个约束对输出没有影响,微小的变化会给出其他错误的答案。看起来答案应该是 [1, 1, 1, 0, 0],它给出了可接受的温度序列 [-1, -2, -3, -1, 1]。如何使用 PuLP 或其他免费的 python 优化库指定控制输入的顺序性质?

最简单且最不容易出错的方法是为您的问题创建一组新的辅助变量,以跟踪冰箱在每个时间间隔内的温度。这些不是 'primary decision variables',因为您不能直接选择它们 - 而它们的值受到冰箱的 on/off 决策变量的限制。

然后您将对这些温度状态变量添加约束以表示动态。所以在未经测试的代码中:

l_plus_1 = list(range(6))
fridge_temp = LpVariable.dicts("fridge_temp", l_plus_1, cat='Continuos')
fridge_temp[0] = init_temp   # initial temperature of fridge - a known value

for i in l:
    prob += fridge_temp[i+1] == fridge_temp[i] + 2 - 3*s[i]

然后您可以发送这些新 fridge_temp 变量的 min/max 温度限制。

请注意,在上文中,我假设冰箱温度变量的定义间隔比冰箱的 on/off 决策多一个间隔。冰箱温度变量表示区间 start 处的温度 - 多一个意味着我们可以确保冰箱的最终温度是可接受的。