背景参考: [1] 通过对几个 pyomo 子集进行条件求和来生成约束
我通过@Airsquid提供的解决方案获得了上述问题的正确解决方案,但是,在可用能源供应量较少(相对于请求数量)的情况下,我面临着较长的运行时间。
我添加了一些早期停止设置,这对于大多数情况来说都很好,因为我不一定需要完全最优的解决方案,只需一个足够好的解决方案即可。 solver.options = { 'sec': 600, 'threads':6, 'ratio': 5} #提前停止设置
我想尝试根据@Airsquid在[2]中给出的建议重构代码: “在这种情况下,您只需从 start 变量“了解请求的一切”。因此,您可以进行一次大的重构,并可能删除一些辅助变量。如果它在第 5 期开始并且请求是对于具有一定固定功率量的 10 个周期,您不需要一个变量来知道它是否在周期 8 中运行 - 它是!对于该周期内的分配也是如此。您仍然需要一个变量来跟踪为每个请求分配多少功率。”
由于我对线性编程和 pyomo 还很陌生,因此在制定语法时遇到了一些问题。
我只能想到两种方法来做到这一点:
方法一: 定义 m.start[t,r] == 1 和 m.start[t+request_length_periods,r] 之间 m.dispatch[t,r] 变量的值,语法的粗略尝试是以下!我知道这是不正确的以及为什么它不正确,但我不知道解决方案是什么。
@m.Constraint(m.windows_flat)
def request_length(m, t, r):
request_length_periods = int(m.duration_periods[r])
request_periods = set(list(range(t,t+request_length_periods+1)))
return (m.dispatch[t,r] == m.request_power_size[r] for t in request_periods if m.start[t,r] == 1)
这毫不奇怪地给了我一个错误:
ERROR: Rule failed when generating expression for Constraint request_length
with index (910192, 88): ValueError: Constraint 'request_length[910192,88]'
does not have a proper value. Found '<generator object
generalOptimisation.<locals>.request_length.<locals>.<genexpr> at
0x168022b20>' Expecting a tuple or relational expression. Examples:
sum(model.costs) == model.income (0, model.price[item], 50)
ERROR: Constructing component 'request_length' from data=None failed:
ValueError: Constraint 'request_length[910192,88]' does not have a proper
value. Found '<generator object
我知道我如何定义 request_periods 是不正确的,但我不知道如何在不知道何时 m.start[t,r] == 1 的情况下定义这个集合?
方法2: m.start[t,r] *(m.dispatch[t,r]...m.dispatch[t+request_length_periods,r]) == 1 (我不知道 Pyomo 中的语法是什么,我知道这会使解决方案非线性)
如果有人对如何根据@Airsquid的建议正确地制定这个建议有任何建议,我将非常感激,目前我计划进一步放宽提前停止以解决这个问题,这并不完全可取。
也欢迎任何关于提前停止设置的输入 - 也许系统超时 600 秒是不现实的!
这是对原始代码的重构,它使用 1 个二进制变量进行调度。这里有几件事可能可以稍微重构一下,但它展示了一种解决方法。它还假设为每个请求提供的功率在各个时期都是恒定的。
几次 ping 后捕获了相当独特的结果(如图所示)
# energy assignment
import sys
from collections import defaultdict
from random import randint
import pyomo.environ as pyo
from matplotlib import pyplot as plt
### DATA
num_periods = 12
periods = tuple(range(num_periods))
# (earliest, latest)
request_timespan = {
"r1": (0, 7),
"r2": (2, 3),
"r3": (1, 5),
}
request_power_per_period = {"r1": 5, "r2": 4, "r3": 8}
request_duration = {"r1": 4, "r2": 8, "r3": 5}
power_avail = dict(zip(periods, (randint(8, 20) for _ in range(num_periods))))
# prevent mind-numbing errors:
assert request_timespan.keys() == request_power_per_period.keys()
assert request_timespan.keys() == request_duration.keys()
# we know that we are going to need to sum power across each period at some point, so one
# approach is to determine which request are eligible to be satisfied in each period. So,
# you could probably either make a dictionary of requests: {timeslots} or perhaps better:
# timeslot: {eligible requests}... either way can work.
eiligible_starts = defaultdict(list)
for r, (start, end) in request_timespan.items():
for t in [
t for t in range(start, end + 1) if t + request_duration[r] <= num_periods
]:
eiligible_starts[t].append(r)
# check it...
print(eiligible_starts)
### MODEL BUILD
m = pyo.ConcreteModel("dispatch")
### SETS
m.T = pyo.Set(initialize=periods)
m.R = pyo.Set(initialize=tuple(request_timespan.keys()))
# Note: This will be an "indexed" set, where we have sets indexed by some index, in this case, m.T
m.windows = pyo.Set(
m.T,
initialize=eiligible_starts,
within=m.R,
doc="requests eligible in each timeslot",
)
# We can make a flat-set here which will be a good basis for the dispatch variable
m.windows_flat = pyo.Set(
initialize={(t, r) for t in eiligible_starts for r in eiligible_starts[t]},
within=m.T * m.R,
)
### PARAMS
m.request_size = pyo.Param(m.R, initialize=request_power_per_period)
m.power_limit = pyo.Param(m.T, initialize=power_avail)
### VARS
m.dispatch = pyo.Var(
m.windows_flat, domain=pyo.Binary, doc="dispatch power in timeslot t to request r"
)
### OBJ
m.obj = pyo.Objective(
expr=sum(m.dispatch[t, r] for (t, r) in m.windows_flat), sense=pyo.maximize
)
### CONSTRAINTS
@m.Constraint(m.R)
def satisfy_only_once(m, r):
return sum(m.dispatch[t, rr] for (t, rr) in m.windows_flat if r == rr) <= 1
@m.Constraint(m.T)
def supply_limit(m, t):
# we need to sum across all dispatches that could be running in this period
possible_dispatches = {
(tt, r) for (tt, r) in m.windows_flat if 0 <= t - tt < request_duration[r]
}
if not possible_dispatches:
return pyo.Constraint.Skip
return (
sum(m.dispatch[tt, r] * m.request_size[r] for tt, r in possible_dispatches)
<= m.power_limit[t]
)
# check it
m.pprint()
# solve it
solver = pyo.SolverFactory("cbc")
res = solver.solve(m)
print(res)
m.dispatch.display()
plt.step(periods, [power_avail[p] for p in periods], color="g")
assigned_periods = {}
for t, r in m.dispatch:
if pyo.value(m.dispatch[t, r]) > 0.5: # request was assigned
assigned_periods[r] = list(range(t, t + request_duration[r]))
print("hit", t, r)
total_period_power_assigned = []
for t in m.T:
assigned = 0
for r in m.R:
if t in assigned_periods.get(r, set()):
assigned += request_power_per_period[r]
total_period_power_assigned.append(assigned)
print(total_period_power_assigned)
plt.step(periods, total_period_power_assigned)
plt.show()
dispatch : dispatch power in timeslot t to request r
Size=15, Index=windows_flat
Key : Lower : Value : Upper : Fixed : Stale : Domain
(0, 'r1') : 0 : 0.0 : 1 : False : False : Binary
(1, 'r1') : 0 : 1.0 : 1 : False : False : Binary
(1, 'r3') : 0 : 0.0 : 1 : False : False : Binary
(2, 'r1') : 0 : 0.0 : 1 : False : False : Binary
(2, 'r2') : 0 : 0.0 : 1 : False : False : Binary
(2, 'r3') : 0 : 0.0 : 1 : False : False : Binary
(3, 'r1') : 0 : 0.0 : 1 : False : False : Binary
(3, 'r2') : 0 : 1.0 : 1 : False : False : Binary
(3, 'r3') : 0 : 0.0 : 1 : False : False : Binary
(4, 'r1') : 0 : 0.0 : 1 : False : False : Binary
(4, 'r3') : 0 : 1.0 : 1 : False : False : Binary
(5, 'r1') : 0 : 0.0 : 1 : False : False : Binary
(5, 'r3') : 0 : 0.0 : 1 : False : False : Binary
(6, 'r1') : 0 : 0.0 : 1 : False : False : Binary
(7, 'r1') : 0 : 0.0 : 1 : False : False : Binary
hit 1 r1
hit 4 r3
hit 3 r2
{'r1': [1, 2, 3, 4], 'r3': [4, 5, 6, 7, 8], 'r2': [3, 4, 5, 6, 7, 8, 9, 10]}
[0, 5, 5, 9, 17, 12, 12, 12, 12, 4, 4, 0]