[英]scipy.optimize.minimize SLSQP indirect constraints problem
I am struggling with minimize
( method=SLSQL
) and need help.我在
minimize
( method=SLSQL
) 方面苦苦挣扎,需要帮助。 This is a simplified car battery (dis)charging problem to prop up the power grid during reduced stability (peak demand).这是一个简化的汽车电池(放电)充电问题,用于在稳定性降低(峰值需求)期间支撑电网。 What I expect to happen is that during such instability, the battery gets discharged as much as possible when the price of electricity is highest and charged when the price is lowest, and the grid is stable again.
我期望发生的情况是,在这种不稳定期间,电池会在电价最高时尽可能多地放电,并在电价最低时充电,然后电网再次稳定。 Instead, the optimization happily says it succeeded but either didn't optimize anything or ignores one or more constraints.
相反,优化很高兴地说它成功了,但要么没有优化任何东西,要么忽略了一个或多个约束。 I played around with the parameters passed to SLSQP, and when I set
esp=1
, things start happening, but it never comes close to discharging the battery completely, and does not attempt to recharge (much).我尝试使用传递给 SLSQP 的参数,当我设置
esp=1
时,事情开始发生,但它永远不会接近电池完全放电,并且不会尝试充电(很多)。 It can happen, that it discharges the battery beyond zero to -2 or so.可能会发生,它将电池放电超过零至 -2 左右。
Here someone suggested using transformations to enforce variable limits, but in this case, it's an indirect constraint applied to the combination of several variables. 这里有人建议使用转换来强制执行变量限制,但在这种情况下,它是应用于多个变量组合的间接约束。 Are transforms still an option?
转换仍然是一种选择吗?
The constraint that the battery should be recharged to 1 at the end of the cycle is ignored, too.电池应在循环结束时充电至 1 的约束也被忽略。
I imagine SLSQP is not the best search algorithm for this, and I would be thankful for suggestions for better options for the long term.我想 SLSQP 不是最好的搜索算法,对于长期更好的选择的建议,我将不胜感激。 Even telling me what kind of problem (in optimization terms) this is would be very much appreciated.
即使告诉我这是什么样的问题(在优化方面),我也会非常感激。 Short term, I would be very thankful to point out how I can get SLSQP to work.
短期内,我将非常感谢指出如何让 SLSQP 工作。
UPDATE: Someone told me that this might be a linear programming problem.更新:有人告诉我这可能是一个线性规划问题。 How does that work with such flexible price data if that is the case?
如果是这样的话,它如何与如此灵活的价格数据一起使用? Where can I find an example explaining how to tackle it?
我在哪里可以找到解释如何解决它的示例?
import numpy as np
from scipy.optimize import minimize, OptimizeResult
from typing import TypedDict
SOC_MAX = 1.0 # normalized
ONE_MINUTE= 60
CHARGING_SOC_PER_SEC = SOC_MAX / (3*60*60) # fully charge in 3h
CHARGE_TIME_MAX_SECONDS = 3*60*60 # in seconds
time_a = np.arange(171901, step=900)
price_a = np.array([
143.08, 143.08, 143.08, 140.25, 140.25, 140.25, 140.25, 130.84,
130.84, 130.84, 130.84, 130.03, 130.03, 130.03, 130.03, 138.7 ,
138.7 , 138.7 , 138.7 , 169.95, 169.95, 169.95, 169.95, 204.37,
204.37, 204.37, 204.37, 230.09, 230.09, 230.09, 230.09, 219.09,
219.09, 219.09, 219.09, 207.62, 207.62, 207.62, 207.62, 213.26,
213.26, 213.26, 213.26, 206.2 , 206.2 , 206.2 , 206.2 , 211.11,
211.11, 211.11, 211.11, 215.9 , 215.9 , 215.9 , 215.9 , 227.28,
227.28, 227.28, 227.28, 234.97, 234.97, 234.97, 234.97, 257.99,
257.99, 257.99, 257.99, 274.33, 274.33, 274.33, 274.33, 236. ,
236. , 236. , 236. , 202.95, 202.95, 202.95, 202.95, 183.63,
183.63, 183.63, 183.63, 165.92, 165.92, 165.92, 165.92, 145.07,
145.07, 145.07, 145.07, 165.39, 165.39, 165.39, 165.39, 152.51,
152.51, 152.51, 152.51, 145.87, 145.87, 145.87, 145.87, 143.06,
143.06, 143.06, 143.06, 145.38, 145.38, 145.38, 145.38, 159.61,
159.61, 159.61, 159.61, 183.77, 183.77, 183.77, 183.77, 210.8 ,
210.8 , 210.8 , 210.8 , 213.77, 213.77, 213.77, 213.77, 203.33,
203.33, 203.33, 203.33, 200.97, 200.97, 200.97, 200.97, 199.02,
199.02, 199.02, 199.02, 193.72, 193.72, 193.72, 193.72, 179.7 ,
179.7 , 179.7 , 179.7 , 165.57, 165.57, 165.57, 165.57, 163.94,
163.94, 163.94, 163.94, 178.01, 178.01, 178.01, 178.01, 200.93,
200.93, 200.93, 200.93, 201.01, 201.01, 201.01, 201.01, 193.47,
193.47, 193.47, 193.47, 165.32, 165.32, 165.32, 165.32, 135.09,
135.09, 135.09, 135.09, 125.56, 125.56, 125.56, 125.56, 104.86,
104.86, 104.86, 104.86, 109.41, 109.41, 109.41, 109.41, 111.09])
stability_ranges = np.array([[ 33, 53],
[ 71, 119],
[131, 191]])
instability_ranges = np.array([[ 21, 32],
[ 54, 70],
[120, 130]])
booking_dt = np.dtype([("start", int),
("duration", int),
("soc", float), # state of charge of the battery
("delta_soc", float), # change in battery charge
("price", float)]) # this is what the user is billed.
class OptimisationItem(TypedDict, total=False):
start: int # this might be not needed, as its the key in the dict
duration: int
soc: float # state of charge of the battery
delta_soc: float # change in battery charge
price: float # this is what the user is billed.
bound_high: int
bound_low: int
def integrate(start, duration):
x_start = start
x_end = start + duration
x_data = np.linspace(x_start, x_end, 10)
y_interp = np.interp(x_data, time_a, price_a)
area = np.trapz(y_interp, x_data)
return area
"""the battery can not be charged more than 100% and not less than 0%"""
def soc_constraint(x):
soc = SOC_MAX
durations = x[1::2]
for i, j in durations.reshape(-1, 2):
soc -= i * CHARGING_SOC_PER_SEC
if soc < 0:
break
soc += j * CHARGING_SOC_PER_SEC
if soc > SOC_MAX:
soc = SOC_MAX - soc
break
return soc
"""the (dis)charging should happen only within the bounds"""
def bounds_constraint(x, bounds):
end_points= bounds - (x[::2] + x[1::2])
smallest_end_point = np.min(end_points)
return smallest_end_point
"""the battery should be fully charge at the end of the cycle"""
def end_constraint(x, plan):
final_soc_gap = x[-1] * CHARGING_SOC_PER_SEC - (SOC_MAX - plan["soc"][- 1])
return final_soc_gap
def objective_func(x: np.array, plan) -> float:
events= x.reshape(-1, 2)
# insert the optimizsation variables into the plan
plan["start"] = events[:,0]
plan["duration"] = events[:,1]
# start values for soc=SOC_MAX and delta_soc: 0
plan["soc"][-1] = SOC_MAX
plan["delta_soc"][-1] = 0
plan["duration"][-1] = 0
# straighten out the plan, calculate price and soc
for step in range(len(plan)):
plan["soc"][step] = plan["soc"][step - 1] + plan["delta_soc"][step - 1]
if step % 2 == 0:
plan["delta_soc"][step] = - plan["duration"][step] * CHARGING_SOC_PER_SEC
plan["price"][step] = - integrate(plan["start"][step], plan["duration"][step])
else:
plan["delta_soc"][step] = plan["duration"][step] * CHARGING_SOC_PER_SEC
plan["price"][step] = + integrate(plan["start"][step], plan["duration"][step])
price: float = plan["price"].sum()
return price
def optimise_discharge_plan(plan: np.array, discharge_events_d) -> np.array:
x_initial = []
bounds = []
constraints = []
bounds_high_l = []
for time in plan["start"]:
data_set = discharge_events_d[time]
x_initial.append(time)
x_initial.append(data_set["duration"])
bound_low = data_set["bound_low"]
bound_high = data_set["bound_high"]
bounds_high_l.append(bound_high)
bounds.append((bound_low, bound_high))
bounds.append( (0, min(CHARGE_TIME_MAX_SECONDS, bound_high - bound_low)))
bounds_high = np.array(bounds_high_l)
constraints.append({"type": "ineq", "fun": bounds_constraint, "args": (bounds_high,)})
constraints.append({"type": "eq", "fun": end_constraint, "args": (plan,)})
constraints.append({"type": "ineq", "fun": soc_constraint})
result:OptimizeResult = minimize(objective_func, np.array(x_initial),
args=plan,
method="SLSQP",
bounds=bounds,
constraints=constraints,
#options={"eps": 1 , "maxiter": 1000, "ftol": 1.0, "disp": True},
)
print(result.x.reshape(-1, 4),"\n", result, "\n",plan)
def main():
discharge_events_l:list[booking_dt] = []
discharge_events_d: dict = {}
for range_cnt, (instability_range, stability_range) in enumerate(zip(instability_ranges, stability_ranges)):
# build up list of discharge times, that we can modify in the optimisation step
if instability_range[0] == instability_range[1]:
time_max_price = instability_range[0]
else:
max_index = np.argmax(price_a[instability_range[0]:instability_range[1]])
time_max_price = time_a[instability_range[0] + max_index]
instability_item: OptimisationItem = {"bound_low": time_a[instability_range[0]],
"bound_high": time_a[instability_range[1]]+15*60 -1,
"duration": ONE_MINUTE, # inital value
"start": time_max_price,}
discharge_events_d[time_max_price] = instability_item
if stability_range[0] == stability_range[1]:
time_min_price = stability_range[0]
else:
min_index = np.argmin(price_a[stability_range[0]:stability_range[1]])
time_min_price = time_a[stability_range[0] + min_index]
stability_item: OptimisationItem = {"bound_low": time_a[stability_range[0]],
"bound_high": time_a[stability_range[1]]+15*60 -1,
"duration": ONE_MINUTE,
"start": time_min_price,}
discharge_events_d[time_min_price] = stability_item
event: booking_dt = np.zeros(2, dtype=booking_dt)
# discharge
event["start"][0] = time_max_price
event["duration"][0] = ONE_MINUTE
# charge
event["start"][1] = time_min_price
event["duration"][1] = ONE_MINUTE
discharge_events_l.append(event)
discharge_plan = np.array(discharge_events_l, dtype=booking_dt).reshape(-1)
optimise_discharge_plan(discharge_plan, discharge_events_d)
if __name__ == '__main__':
main()
One minor issue why the SLSQP algorithm failed was that it was too greedy and didn't want to buy/charge the battery. SLSQP 算法失败的一个小问题是它过于贪婪,不想购买/充电电池。 I introduced a medium reference against which the algorithm buys and sells, which got it to work "well enough" for now.
我引入了一个媒介参考,算法根据该参考进行买卖,这让它现在“足够好”地工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.