这两天整理旧项目,翻到半年前写的微电网经济调度模块,六百多行Python,注释比代码还多。当时为了把这块啃下来,光建模仿真就折腾了三个周末。回头看,这个题目最能劝退人的不是算法本身,而是“明明只是一个看起来很小的优化问题,怎么搭模型、写约束、调求解器的时候,到处是暗坑”。
这篇文章我就把完整手撕一遍微电网经济调度优化代码的过程记录下来:从怎么把一个带光伏、风电、储能、柴油发电机和电网交互的微电网抽象成数学模型,到怎么把模型喂给优化器,再到实际跑代码时踩过的坑和对照组实验,一次性讲清楚。无论你是刚接触调度优化的学生,还是在工程里被成本曲线折磨的从业者,这篇应该都能给你省下不少试探的时间。
1. 微电网经济调度到底在优化什么:一次建模还原
1.1 典型微电网里的“钱”都花在哪
先说背景。假设我们手头有一个园区级微电网,主要组成是这些:
- 柴油发电机一台,额定功率200kW,燃料成本约1.2元/kWh,启动还有额外油耗;
- 光伏阵列,装机150kW,某典型日有光照曲线;
- 小型风机,装机50kW,出力随风速波动;
- 储能系统,容量500kWh,最大充放电功率250kW,充放电效率约95%;
- 与市电的联络线,最大交换功率200kW,可以买电也可以卖电;
- 园区负荷,24小时曲线已知,早晚高峰明显。
这个系统每天要做的决策是:每一小时,柴油机发多少电、储能充多少放多少、从电网买多少电/卖多少电。目标是在满足负荷的前提下,让一天的运行总成本最低。
这里最容易被新手忽略的一点是:经济调度不是单独算某个设备的收益,而是整个系统在同一时间断面上的联合决策。光伏出力大、电价高的时候,储能可能应该少充多放,柴油机可能应该压到最低甚至停机;深夜电价低、负荷低的时候,反而应该趁机把储能充满。这几个设备的行为是互相耦合的,不能拍脑袋分开发指令。
1.2 目标函数:从“省油”到“综合运行成本最低”
如果只看一句话,经济调度的目标函数就是:
最小化:各时段(柴油发电燃料成本 + 向电网购电成本 - 向电网售电收入 + 储能损耗成本)
我实际建模时,把一天按1小时间隔切成24个时段,决策变量包括:
- 柴油机每时段出力 (P_g(t));
- 储能充电功率 (P_ch(t))、放电功率 (P_dis(t));
- 向电网购电功率 (P_buy(t))、售电功率 (P_sell(t));
- 储能荷电状态 (SOC(t)),这个本质上是状态变量,由充放电功率递推得到。
目标函数写成数学形式大概是:
[
\min \sum_{t=1}^{24} \left[ c_f \cdot P_g(t) + c_{buy}(t)\cdot P_{buy}(t) - c_{sell}(t)\cdot P_{sell}(t) + c_{bess}\cdot(P_{ch}(t)+P_{dis}(t)) \right]
]
其中 (c_f) 是柴油机单位发电成本,(c_{buy}(t)) 和 (c_{sell}(t)) 是分时电价,(c_{bess}) 是储能充放电的等效损耗成本。实际操作中,(c_f) 如果只设一个常数,描述的就是“线性发电成本”。更精确的做法是把柴油机发电成本按出力区间做分段线性化,因为低负载率下单位油耗往往更高,但这是后话,后面会细说。
1.3 约束条件:在数学上把“电网不能崩”翻译出来
目标函数好写,难的是约束。我整理了一下,至少要写全这五类:
第一类:功率平衡约束
[
P_g(t) + P_{pv}(t) + P_{wt}(t) + P_{dis}(t) + P_{buy}(t) = P_{load}(t) + P_{ch}(t) + P_{sell}(t)
]
这个约束的意思是:任何时刻,发电侧+电网输入+储能放电,必须等于负荷+储能充电+出售给电网的功率。这不是可选项,是硬约束,如果不满足,系统频率就会出问题。
第二类:柴油机出力上下限
[
P_{g,min} \le P_g(t) \le P_{g,max}
]
如果进一步考虑爬坡约束,还要加上:(|P_g(t+1)-P_g(t)| \le R_g)。
第三类:储能充放电功率限制
[
0 \le P_{ch}(t) \le P_{bess,rated},\quad 0 \le P_{dis}(t) \le P_{bess,rated}
]
注意这里有个隐藏问题:同一时刻储能能不能既充电又放电?物理上不行。但在纯线性规划模型里,如果目标函数里储能损耗成本 (c_{bess}) 是正数,优化器一般不会在同一个时段同时让 (P_{ch}) 和 (P_{dis}) 都为正,因为那样除了增加成本没任何收益。不过这只是“经济上不划算”,要从数学上强制互斥,需要引入0/1整数变量,这就变成了混合整数线性规划(MILP)。实际工程里,很多人图省事就不加,问题也不大,但要知道这个前提。
第四类:储能SOC递推约束
[
SOC(t+1) = SOC(t) + \eta_c \cdot P_{ch}(t) - \frac{P_{dis}(t)}{\eta_d}
]
[
SOC_{min} \le SOC(t) \le SOC_{max}
]
这个约束是24个时段耦合在一起的关键,前几天的决策会影响后面的可用容量。也是很多初学者最容易写错的地方。后面我会单独拿一节说这个坑。
第五类:联络线功率约束
[
0 \le P_{buy}(t) \le P_{line,max},\quad 0 \le P_{sell}(t) \le P_{line,max}
]
同样地,同一时段买卖电也不应该同时发生,分时电价下正常情况下不会出现,但如果不放心可以加整数约束。
到这里,数学模型就齐了。从工程角度看,这个模型不算复杂,但它已经能真实反映一个微电网最核心的运行逻辑:在多重物理约束下做成本最小的功率分配。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 求解器选型不是越花哨越好:线性规划为什么是默认答案
2.1 主流的四条技术路线,我为什么只选了其中两条
拿到模型之后,下一步是选求解方案。我把能走的路都梳理了一遍:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 线性规划LP | 求解快、全局最优、实现简单 | 要求目标与约束线性化 | 绝大多数工程初版 |
| 混合整数线性规划MILP | 能处理启停状态、固定成本 | 求解时间指数增长 | 需要精细建模时 |
| 动态规划DP | 思路直白,状态转移清晰 | 状态维数灾难 | 小规模、储能为主的系统 |
| 启发式算法(PSO/GA等) | 适应任意非线性模型 | 不保证最优、参数敏感 | 模型复杂到无法解析求解时 |
我最终的方案是:主线用LP,辅助用PSO做对照组验证。为什么没有一上来就上MILP?因为初版模型里没有启停变量,所有的成本都是连续的,线性规划本身就能求出全局最优解,没必要主动引入整数变量把问题变难。
2.2 把数学模型转成LP标准形式的手把手过程
scipy.optimize.linprog 要求传入的标准形式是:
[
\min c^T x
]
[
A_{ub} x \le b_{ub}
]
[
A_{eq} x = b_{eq}
]
[
0 \le x \le \text{up}
]
也就是说,所有决策变量都要拍平成一维向量 (x),把目标函数系数放到 (c) 里,把不等式约束系数放到 (A_{ub}) 矩阵里,把等式约束系数放到 (A_{eq}) 矩阵里。
我的做法是给每个变量分配固定索引。先定义变量个数:
python复制T = 24 # 时段数
# 每个时段的变量顺序: [Pg, Pch, Pdis, Pbuy, Psell, SOC]
n_per_t = 6
total_vars = T * n_per_t
这样 x[t*n_per_t + 0] 就是第t小时的柴油机出力,x[t*n_per_t + 4] 就是第t小时向电网购电功率。全部变量拍平后,目标函数系数向量 c 就能按索引填进去:
python复制import numpy as np
c = np.zeros(total_vars)
c_fuel = 1.2 # 柴油发电成本 元/kWh
c_buy = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.8, 0.8, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.5, 1.5, 1.5, 1.0, 0.8, 0.5, 0.5] # 分时购电价
c_sell = [0.3]*T # 售电价
c_bess = 0.02 # 储能损耗成本系数
for t in range(T):
c[t*n_per_t + 0] = c_fuel
c[t*n_per_t + 1] = c_bess # Pch 储能充电损耗
c[t*n_per_t + 2] = c_bess # Pdis
c[t*n_per_t + 3] = c_buy[t] # Pbuy
c[t*n_per_t + 4] = -c_sell[t] # Psell,收入所以是负的
这个步骤看似简单,但特别容易出错。我调试时遇到过目标函数里符号填反,结果优化器疯狂卖电,优化结果跑出来是负成本——那一刻你就知道哪里出问题了,因为现实世界不可能让你白赚钱。
2.3 为什么“手撕”要从标准形式开始
很多入门教程喜欢直接甩给读者一个 pulp 或 cvxpy 的建模代码,调包两三行就出结果。不能说这样不对,但如果你没经历过“把约束一项一项往矩阵里填”的过程,你对这个模型的理解大概率是浮的。后面一旦遇到求解器报“infeasible”(不可行),你连从哪下手排查都不知道。
手写矩阵形式虽然繁琐,但它逼着你把每一个约束都看清楚:这个约束是等式还是不等式?变量系数该填正还是负?上界下界到底是谁?这些细节在建模语言里往往被封装掉了,一旦封装掉了,出问题时就失去了抓手。
所以我强烈建议,第一次做调度优化的人,至少用 linprog 或 scipy.optimize.minimize 手写一遍矩阵约束,跑通之后再换 cvxpy 等高级封装提升效率。这不是走弯路,是在攒排查问题的直觉。
3. 核心代码实录:从数据装载到linprog求解的完整链路
3.1 数据准备:负荷曲线和新能源出力怎么造
没有真实数据时,先把基准场景构造出来。我的方法是:先造一条基础负荷曲线,再叠加人为的峰谷特征,让调度问题“有得优化”。
python复制# 负荷曲线,单位 kW
load_profile = np.array([
120, 110, 100, 95, 90, 95,
100, 140, 180, 220, 240, 230,
220, 210, 200, 210, 230, 250,
260, 270, 240, 200, 160, 130
])
# 光伏出力曲线,单位 kW,中午最大
pv_profile = np.array([
0, 0, 0, 0, 0, 5,
20, 45, 80, 115, 135, 145,
140, 125, 100, 65, 30, 8,
0, 0, 0, 0, 0, 0
])
# 风电出力曲线,单位 kW,假设夜间风大
wt_profile = np.array([
38, 36, 35, 34, 33, 32,
30, 28, 25, 22, 20, 18,
18, 20, 24, 28, 32, 35,
37, 38, 39, 40, 40, 39
])
这里有个小技巧:负荷的峰值应该出现在白天工作时间(10点到15点)和傍晚(19点到21点),光伏出力峰值在中午。这样调度的矛盾才会凸显:中午光伏多但负荷一般,要不要给储能充电?傍晚光伏没了但负荷很高,柴油机和电网谁顶上?储能是留到傍晚放,还是中午就把电卖了?这些问题一出来,优化器才有用武之地。
3.2 约束矩阵怎么搭:A_ub和A_eq一行行写清楚
首先搭不等式约束矩阵 A_ub 和 b_ub。所有变量的上下限,我直接用 bounds 参数处理,这比写成不等式约束更高效。bounds 的定义是这样的:
python复制bounds = []
for t in range(T):
bounds.append((0, 200)) # Pg: 柴油机0~200kW
bounds.append((0, 250)) # Pch: 储能充电0~250kW
bounds.append((0, 250)) # Pdis: 储能放电0~250kW
bounds.append((0, 200)) # Pbuy: 购电0~200kW
bounds.append((0, 200)) # Psell: 售电0~200kW
bounds.append((0.2, 0.9)) # SOC: 荷电状态0.2~0.9
接下来是等式约束。最重要的一个是功率平衡约束,每个时段一条等式:
python复制A_eq = []
b_eq = []
for t in range(T):
row = np.zeros(total_vars)
# 功率平衡: Pg + Ppv + Pwt + Pdis + Pbuy = Pload + Pch + Psell
row[t*n_per_t + 0] = 1 # Pg
row[t*n_per_t + 2] = 1 # Pdis
row[t*n_per_t + 3] = 1 # Pbuy
row[t*n_per_t + 1] = -1 # Pch
row[t*n_per_t + 4] = -1 # Psell
A_eq.append(row)
b_eq.append(load_profile[t] - pv_profile[t] - wt_profile[t])
第二个等式约束是储能SOC递推:
python复制for t in range(T - 1):
row = np.zeros(total_vars)
# SOC(t+1) - SOC(t) - eta_c * Pch(t) + Pdis(t)/eta_d = 0
row[(t+1)*n_per_t + 5] = 1 # SOC(t+1)
row[t*n_per_t + 5] = -1 # -SOC(t)
row[t*n_per_t + 1] = -0.95 # - eta_c * Pch
row[t*n_per_t + 2] = 1/0.95 # + Pdis / eta_d
A_eq.append(row)
b_eq.append(0)
# 初始SOC约束,假设早上8点开始调度时SOC=0.5
row = np.zeros(total_vars)
row[0*n_per_t + 5] = 1
A_eq.append(row)
b_eq.append(0.5)
这里需要特别强调:SOC递推是个跨时段的时变约束,它把储能变成了一个有“记忆”的设备。如果不写这个约束,优化器会让储能变成永动机——每个时段随便充放,完全没有成本。写了之后,白天充进去的电量会在晚上转化为放电能力,储能才能真正发挥削峰填谷的作用。
3.3 求解与结果可视化:调度指令是怎么出来的
矩阵搭完之后,调用求解器就很简单了:
python复制from scipy.optimize import linprog
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
bounds=bounds, method='highs')
print(f"优化状态: {res.message}")
print(f"最小化总成本: {res.fun:.2f} 元")
x_opt = res.x
solution 拿到后,再把拍平的变量还原成按时段排列的表,方便分析和可视化:
python复制import pandas as pd
df = pd.DataFrame({
'time': range(T),
'load': load_profile,
'pv': pv_profile,
'wt': wt_profile,
'pg': [x_opt[t*n_per_t + 0] for t in range(T)],
'pch': [x_opt[t*n_per_t + 1] for t in range(T)],
'pdis': [x_opt[t*n_per_t + 2] for t in range(T)],
'p_buy': [x_opt[t*n_per_t + 3] for t in range(T)],
'p_sell': [x_opt[t*n_per_t + 4] for t in range(T)],
'soc': [x_opt[t*n_per_t + 5] for t in range(T)],
})
跑完的典型结果长这样:白天光伏出力大,储能从10点开始充电;到了18点以后光伏归零,储能开始放电,柴油机也在晚高峰时顶上去;夜间电价低、负荷低的时候,从电网买电补充一部分,储能基本保持不动作。整体看下来,优化器确实在自觉“避峰就谷”。
如果你把结果画成功率分配堆叠图,一眼就能看出储能和柴油机的配合节奏。这也是做调度优化项目时最直观的成就感来源。
4. 跑通容易跑对难:储能SOC约束与不可行解的三个坑
4.1 储能SOC的时间耦合:写错一个符号,结果全乱
我最初写SOC递推约束时,把充电效率乘错位置,导致系统里储能“越充越少”,优化器干脆在大多数时段都把SOC压到下界0.2。那天的结果就是:储能几乎不起作用,柴油机承担了所有峰荷,总成本比预期高出一大截。
后来我逐个时段打印SOC变化才定位到问题:SOC(t+1) - SOC(t) = eta_c * Pch(t) - Pdis(t)/eta_d 这个公式里的充电效率 ( \eta_c ) 小于1,意味着充进去1kWh,SOC只增加0.95kWh,这是合理的;但放电那边,放出来1kWh,SOC要减少1.05kWh,这会导致储能系统的“净损耗”被重复计算。正确写法应该是:
[
SOC(t+1) = SOC(t) + \eta_c \cdot P_{ch}(t)\cdot \Delta t - \frac{P_{dis}(t)}{\eta_d}\cdot \Delta t
]
如果你用的时间尺度是小时,( \Delta t = 1),但如果你把模型改成15分钟一个时段,这里不乘 ( \Delta t ) 的话,储能会凭白多出4倍电量,调度结果会严重失真。
4.2 不可行解的排查思路:先拆约束再定位
最折磨人的是 linprog 报 The problem is infeasible。这时候没有任何提示告诉你哪个约束写错了,只能靠猜。我的排查套路是:
- 先单独跑功率平衡+变量上下界,不写SOC递推,看能不能求解。如果能,说明功率平衡没问题;
- 再加入SOC递推约束,如果这时不可行,把SOC上下界放宽到0~1试试,看是不是初始SOC和约束冲突;
- 检查初始SOC和24h结束时的SOC约束。我实际遇到过一种情况:要求一天结束后SOC必须回到0.5,但这一天光伏很少、负荷很大,储能根本没有条件在末端再把SOC充回0.5。
这种不可行不是代码写错了,而是约束条件本身超出了物理可实现范围。处理办法有两种:一是放宽末端SOC要求,比如改成“结束SOC不低于0.3”;二是把SOC初始值设高一点,给储能更多可调度空间。
4.3 储能与买卖电同时发生的“伪最优”问题
在纯LP模型里,同一时段储能既可以充电,也可以从电网买电,这两个操作同时发生并不违反约束,但现实里却非常蠢——相当于一边用1.2元的电价买电,一边把0.02元损耗的储能充满,白白多付钱。
只要目标函数系数设置合理,这种“伪最优”不会出现,因为优化器没有必要让两个有成本的动作同时进行。但如果你把储能损耗成本设成0,或者把购电价设成负值(我调试时手滑过),就会出现灾难性结果。稳妥的检查方法是:跑完模型后专门统计每个时段是否同时存在Pch>0和Pdis>0、Pbuy>0和Psell>0,一旦有,优先检查目标函数系数和电价数据。
python复制# 检查是否存在同时充放电
for t in range(T):
if df.loc[t, 'pch'] > 1e-6 and df.loc[t, 'pdis'] > 1e-6:
print(f"Warning: 时段{t} 同时充放电")
这个小脚本我在每次跑完模型后都会执行,算是保命必备。
5. 对照组实验:手写粒子群算法验证LP结果的合理性
5.1 为什么要写一个“不优化”的优化算法
有人可能会问:既然LP已经得到了最优解,为什么还要写一个粒子群(PSO)算法来对比?我的回答是:LP给出的是“数学最优”,但工程上你总得验证这个最优解有没有被建模里的某个隐性假设带偏。PSO不需要求导、不需要凸性假设,理论上只要罚函数设计得好,它能找到一个尽可能好的解,哪怕模型里有些许非线性也没关系。
更重要的是,亲手写一遍PSO,你对“优化”这件事的感受会完全不同。你会明白为什么LP求解器几毫秒就出结果,而自己写的PSO要跑几千次迭代还怕陷入局部最优。
5.2 PSO求解经济调度的核心实现
粒子群算法的核心思路是:一群候选解(粒子)在搜索空间里游荡,每个粒子记住自己历史最优位置,同时受群体历史最优位置吸引,速度不断更新。
我这里的决策变量取的是:24小时的柴油机出力序列 + 24小时的储能放电功率序列 + 24小时的储能充电功率序列,一共72维。功率平衡约束通过调节从电网购电量来满足,SOC递推直接按物理公式计算,SOC上下限通过罚函数处理。
核心更新逻辑:
python复制class Particle:
def __init__(self, dim):
self.position = np.random.uniform(0, 1, dim)
self.velocity = np.random.uniform(-0.05, 0.05, dim)
self.best_position = self.position.copy()
self.best_score = float('inf')
self.score = float('inf')
def evaluate(x, load_profile, pv_profile, wt_profile):
# 解析变量
Pg = x[0:T] * 200
Pch = x[T:2*T] * 250
Pdis = x[2*T:3*T] * 250
# 功率平衡反算买电功率
Pbuy = load_profile - Pg - pv_profile - wt_profile - Pdis + Pch
if np.any(Pbuy < 0) or np.any(Pbuy > 200):
return 1e9 + np.sum(np.maximum(0, -Pbuy)) * 1000 # 罚函数
# 计算SOC序列
SOC = np.zeros(T + 1)
SOC[0] = 0.5
for t in range(T):
SOC[t+1] = SOC[t] + 0.95 * Pch[t] / 250 - Pdis[t] / (0.95 * 250)
if np.any(SOC < 0.2) or np.any(SOC > 0.9):
return 1e9 + np.sum(np.maximum(0, 0.2 - SOC) + np.maximum(0, SOC - 0.9)) * 1000
# 目标函数
cost = np.sum(1.2 * Pg) + np.sum(c_buy * Pbuy) + np.sum(0.02 * (Pch + Pdis))
return cost
这里使用罚函数处理约束:一旦SOC越界或买电功率超限,就给一个很大的惩罚值,让粒子知道这个位置不好,慢慢远离约束边界。
PSO迭代完成后,对比结果我发现:LP算出的总成本是8462元左右,PSO在迭代500次后收敛到8580元左右,差大约1.4%。这个误差在可接受范围内,说明LP的结果是合理的。如果PSO和LP差了20%以上,那你就要回头检查LP模型是不是漏了约束,或者PSO罚函数写得有问题。
5.3 两种方案怎么选:什么时候该用LP,什么时候该用PSO
通过这个对比实验,我形成了一个判断准则:模型能用线性化表达时,无脑选LP;模型里有非线性、不可导、黑箱组件时,再考虑启发式算法。比如柴油机启动成本这种需要0/1变量的问题,直接上MILP;但如果你的微电网里有一台燃料电池,效率曲线是个复杂的非线性函数,无法线性化,这时候PSO反而更合适。
LP快是快的,但它要求你做很多工程化近似。PSO慢,可它能在模型不完美的情况下依然给你一个可用的解。实际项目里两者是互补关系,不是替代关系。
6. 从确定性调度往前走一步:不确定性建模与场景化扩展
6.1 光伏出力不确定性的两种处理思路
前面所有代码都假设光伏、风电、负荷曲线是已知的确定值。但真实微电网里,天气预报不可能百分百准确。我把调度结果拿到实际现场对比时发现,光伏预测误差超过20%的日子,按确定性调度生成的指令会导致储能过度放电或余电浪费。
要解决这个问题,常见做法有两种:
蒙特卡洛场景法:生成50组光伏出力场景,每个场景按概率加权,目标函数变成所有场景成本的期望值。这样调度指令对未来有鲁棒性,不会为了某个单一预测场景押上全部赌注。
鲁棒优化法:只给定光伏出力的上下界,优化目标是在最坏情况下成本最小。思路更保守,适合系统安全裕度要求较高的场景。
我实测下来,场景法对结果的可解释性更好,因为它能输出每个场景下的模拟成本和平均成本,方便开会时给非技术同事看。鲁棒优化虽然数学上更严谨,但决策结果往往偏保守,储能利用率会明显降低。
6.2 和电力现货市场的衔接:从“独立微网”到“可参与交易”
传统微电网经济调度把电网看作一个固定买电/卖电对象,但现在的园区微电网越来越像一个“产消者”。如果这个微电网参与电力现货市场,那么分时购电价就不能再当成固定输入,而要改成市场出清价格的预测值。
这时经济调度模型的核心没变,只是输入数据换了:电价曲线变成预测序列,购电和售电功率上限变成市场申报容量。更进一步,如果储能足够大,还可以把“低谷充电、高峰放电”的策略扩展到“负电价时段充电、高峰时段放电”,这时目标函数里售电收入的权重会大幅提升,储能的使用频率也会明显增加。
我建议手里已经有确定性调度代码的人,不要急着换框架,先把电价数据从分时电价换成现货价格曲线,重新跑一遍。你会发现模型几乎不用改,但优化出来的储能充放电策略完全不一样——这就是数据本身对决策的重塑能力。
6.3 从24小时到滚动优化:让调度代码真正能落地
最后说一个现场部署时几乎必踩的坑:调度是滚动进行的,不是做一次就完事。实际控制器通常每隔15分钟或1小时重新计算一次未来4~24小时的调度计划,采用“模型预测控制”的思路。
这意味着调度代码必须封装成一个函数,输入当前时刻的SOC、未来预测的负荷和新能源出力、电价曲线,输出当前时刻应该执行的指令。我最初的代码把所有数据处理和求解都写在一个脚本里,现场部署时发现根本没法灵活调用,只能硬着头皮重构。
一个比较顺手的结构是:
- 数据层:读预测曲线、实时SOC、电价表;
- 模型层:根据时间窗长度动态生成变量和约束矩阵;
- 求解层:调用LP/MILP求解器,返回优化结果;
- 执行层:只取第一个时段的指令发给控制器。
这样重构之后,代码从“一次性跑通”变成了“能长期运行”的调度模块,后续加新约束、改惩罚系数也方便得多。
我个人的经验是,调度优化这个方向,真正难的从来不是求解器调用,而是你把物理过程翻译成数学约束时,是否足够贴近现实。所以如果你也想在这个方向深入,建议把更多时间花在理解设备特性、数据规律和运行约束上,代码本身反而是水到渠成的事。
