
这段时间一直在做风电、光伏与储能互补调度这一块的内容越做越觉得这个方向有意思。很多人拿到“风-光-储互补调度”这个题目第一反应是去翻各种复杂的论文看一堆两阶段优化、随机规划、混合整数模型结果越看越不知道怎么下手。实际上先把一套确定性时序下的线性规划模型用Python跑通把风电、光伏、电池储能、废弃矿井小型抽水蓄能这四样东西在24小时尺度上的配合关系看明白后面再谈更复杂的扩展才顺理成章。这篇文章就把我做这个项目时的完整思路和代码实现拆开讲清楚适合刚开始接触新能源调度、准备做相关毕设或者想弄清楚储能如何参与调度的朋友。1. 为什么风光储要“互补”调度模型背后的物理逻辑1.1 风光出力和负荷的三方错配什么时候最缺电、什么时候最富余先想清楚一个问题为什么需要储能参与调度因为风电、光伏和负荷这三条曲线天然就错配。典型日里负荷曲线通常有明确的早高峰和晚高峰早高峰集中在7点到9点晚高峰集中在18点到22点。光伏出力呢基本跟着太阳走中午12点到14点达到峰值早高峰和晚高峰都指望不上它。风电的身影就更飘忽了如果按典型的“夜间强、白天弱”来近似凌晨到清晨反而是风电大发的时间段但这时候负荷恰恰处于低谷。把这三条曲线叠在一起就会出现三个典型矛盾深夜到凌晨风电出力冲得高负荷却趴在地板上多余的风电要么弃掉要么想办法存起来中午光伏出力达到一天的顶点负荷水平中等光伏也很容易超过实时需求晚上负荷冲上晚高峰时光伏已经归零风电也未必给力缺的这部分功率必须有东西顶上来。打个比方这就像食堂按固定饭点开饭但光伏这个“摊主”只在中午营业风电这个“摊主”喜欢半夜出摊而食客恰恰集中在早上和晚上。没有储物柜食物就只能浪费掉。储能就是那个储物柜它解决的问题本质上是时间错配下的能量搬移。“互补调度”这个词包含两层意思。第一层是电源之间的互补风电弱时段可以由光伏顶上光伏归零的时段让风电承担主力必要时储能跨时段补充。第二层是储能之间的互补电池和抽水蓄能的技术特性差别很大把它们组合在一起才能既应付得了快速的功率波动又扛得住长时间的能量缺额。1.2 电池和废弃矿井抽蓄快慢两种储能的角色分工电池储能和废弃矿井小型抽水蓄能放在一起不是叠床架屋而是各管一段。电池储能的优势在响应速度。毫秒到秒级就能完成充放电切换单体效率高一般能做到90%以上能量转换路径短特别适合做分钟级到小时级的削峰填谷、一次调频、平抑波动。短板也明显容量规模做大了成本非常可观而且频繁深度充放电会加速寿命衰减连续几小时的大功率放电对电池的容量压力不小。废弃矿井小型抽水蓄能是另一个思路。煤矿关闭后会留下大量竖井、巷道和采空区把这些地下空间改造成抽水蓄能电站的上下水库不需要占用新的地表土地水源直接利用矿井水相当于给废弃矿井找了一个长期用途。这种储能单站规模通常不大但容量做上去了以后持续放电时间可以拉到几个小时甚至更久。缺点是响应时间以分钟计能量转换效率通常在70%到80%之间比电池低一截。用一张表把两者的分工梳理清楚对比项电池储能废弃矿井抽水蓄能响应速度秒级到分钟级分钟级单循环效率90%以上70%-80%典型容量规模数十MWh级数百MWh级适合场景日内短时调节、快速功率支撑长时间能量搬移、跨时段调峰主要成本特性容量成本高、循环寿命有限度电成本低、使用寿命长建设条件选址灵活、布置紧凑依赖矿井地质条件、改造周期长调度模型里这两种储能的角色定位就很清晰电池负责应对快速变化的功率缺口比如光伏出力骤降时迅速顶上抽蓄负责大容量的跨时段搬移比如把凌晨多余的风电存起来留到晚上负荷高峰再放出来。 两者配合得好整个系统的灵活性就上来了。1.3 先算清楚账目标函数与约束的选择依据把物理问题变成数学问题核心是回答三个问题优化什么约束什么用什么规则取舍我做这个模型时假设系统是一副简化的单母线结构风电、光伏、电池、抽蓄同时挂在母线上给负荷供电外部没有火电也没有大电网购电。这种情况下目标函数设计成“系统运行的总费用最小”在逻辑上不完整因为压根没有燃料成本。更自然的做法是设成“系统收益最大化”风电和光伏并网发电带来收益储能充放电消耗运维成本一旦负荷供不上就产生极高的切负荷惩罚。线性形式写出来就是目标最大化 风电收益 光伏收益 - 电池充放电运维成本 - 抽蓄抽水发电运维成本 - 切负荷惩罚约束条件包含四类功率平衡每个时刻风电光伏储能放电切负荷之和必须等于负荷储能充电之和储能状态量约束电池和抽蓄的存电量必须满足SOC转移方程下一时刻存量等于当前存量加上充电存入能量、减去放电消耗能量储能容量边界当前存电量不能低于下限、不能高于上限功率上限风电、光伏出力不能超过当前可用功率储能充放电不能超过额定功率。这里有个设计取舍要说明我特意没有加显式的“弃风弃光惩罚项”而是用风光的发电收益系数来间接引导。因为只要并网发电有正收益求解器在满足所有约束的前提下就会倾向于让风电和光伏尽量多出力少出力自然对应弃电效果和显式惩罚等价。这种写法更贴近电力市场的经济逻辑也方便后续扩展收益模型。把目标函数、等式约束、不等式约束摆齐之后这就是一个标准的线性规划问题。决策变量是实数一不用整数二不用非线性函数用scipy.optimize.linprog就能解24小时的小规模问题在笔记本电脑上几秒钟出结果。2. 数据与场景设定24小时时序从哪来、参数怎么定2.1 示例数据生成负荷、风电、光伏典型日序列做调度模型第一步不是写代码是准备一条合理的时间序列数据。理想情况下应该用实测运行数据比如从电力系统公开算例或者文献附录里找典型日曲线。但为了让大家能直接复现我这里用一组合成的示例数据形态上完全符合典型日特征。代码片段如下import numpy as np T 24 t np.arange(T) # 负荷典型日曲线单位MW # 基础负荷26MW叠加上白天的波动和早晚高峰的凸起 load ( 26.0 6.0 * np.sin(2 * np.pi * (t - 9) / 24) np.where((t 18) (t 22), 8.0, 0.0) np.where((t 7) (t 9), 5.0, 0.0) ) # 风电可用出力单位MW夜间大、白天小 wind_avail np.clip( 30.0 18.0 * np.cos(2 * np.pi * (t - 4) / 24), 5.0, 48.0 ) # 光伏可用出力单位MW用高斯峰近似白天的日照曲线 pv_avail np.clip( 30.0 * np.exp(-((t - 12.5) ** 2) / (2 * 2.5 ** 2)), 0.0, 30.0 )这条负荷曲线的形状是凌晨到清晨在20MW出头早高峰抬升到30MW左右白天在28到32MW之间波动晚高峰冲到40MW以上。风电曲线凌晨最高接近48MW白天低谷掉到15MW上下。光伏曲线中午接近30MW峰值18点以后归零。这样的数据设定能让储能充分发挥作用凌晨风电富余需要把能量存起来白天光照好光伏除了带负荷还有富余晚高峰负荷最高而光伏为零正好把早先存的两笔能量都放出来。如果风、光、负荷三条曲线都刚刚好匹配那这个调度题就没有意义了储能也没有存在价值。需要明确的是这段代码生成的是用于验证建模思路的示例数据。真正的研究项目中请把这三条曲线替换成自己实际的负荷数据和风光出力序列代码框架不需要改动。2.2 储能参数表容量、效率、SOC上下限储能参数是模型的关键输入。参数不对调度结果会离谱到让人怀疑人生。我用的参数如下参数电池储能废弃矿井抽水蓄能额定功率MW5020储能容量MWh100160初始存电量MWh5096SOC运行下限20%20%SOC运行上限90%90%充电效率95%85%放电效率95%85%电池选50MW/100MWh意思是满功率放电可以持续两小时这是“短时、快响应”储能设施的典型配置。抽蓄选20MW/160MWh额定功率小但容量大满功率发电可以持续八小时正好体现“长时储能”的特点。这里有必要解释一下为什么抽蓄功率做得小矿井巷道改造的水力通道流量有限机组的过流能力天然受限所以废弃矿井小型抽水蓄能大多是小功率、大库容的形态。SOC上下限设定也很有讲究。电池不设到0%和100%是因为深度放电和满充都会加速老化而且实际运行中需要留出调节裕量。抽蓄同样不设满因为上下水库都存在死库容和调节禁区。初始存电量设置成50%和60%是为了让模型在零点开始运行时就有一定的调节能力否则初始存量贴着下限凌晨想充电都充不进去。效率参数的位置容易搞错后面踩坑章节我会专门讲。这里先记住一个原则电网向储能输入能量时乘以充电效率储能把能量送回电网时除以放电效率。也就是实际存进去的能量为充电功率乘充电效率实际放出来的能量对存储量的消耗为放电功率除放电效率。2.3 变量与系数的组织方式为什么把功率和SOC分开编号linprog求解器的接口是标准的矩阵形式minimize cx满足 A_ubx b_ub 和 A_eqx b_eq它不认识“风电功率”“SOC”这些语义只认识一个扁平的决策变量向量x。所以建模中最重要的一步是把所有物理变量按固定顺序放进x并且保证构建约束矩阵时索引一一对应。我采用的变量排序是每个时刻t都对应9个变量。变量区间含义0 ~ T-1p_w[t]风电并网出力MWT ~ 2T-1p_pv[t]光伏并网出力MW2T ~ 3T-1p_bc[t]电池充电功率MW3T ~ 4T-1p_bd[t]电池放电功率MW4T ~ 5T-1p_pc[t]抽蓄抽水功率MW5T ~ 6T-1p_pd[t]抽蓄发电功率MW6T ~ 7T-1p_shed[t]切负荷功率MW7T ~ 8T-1e_b[t]电池当前存电量MWh8T ~ 9T-1e_p[t]抽蓄当前存电量MWh功率变量和SOC变量分开编号而不是混在一起是为了构建约束矩阵时更方便。功率平衡约束只需要动功率变量SOC转移约束只动特定时刻的存电量变量和相邻时刻的充放电功率变量两者各管各的逻辑清晰。很多初学的人不习惯这种“扁平化”的变量管理结果就是代码写到一半索引全乱解出来的东西完全没法看。我在做项目时给自己定了一条规矩先在纸上画出变量索引表再写矩阵填充代码。看上去多了一步实际上省掉的是好几个小时的排错时间。3. 用scipy.optimize.linprog实现互补调度核心代码拆解3.1 构建A_eq和A_ub约束矩阵的思路linprog接收的约束是矩阵形式的每一行代表一条约束每一列对应一个变量。构建矩阵的基本思路是先初始化一个全零矩阵然后逐条把物理约束“翻译”成矩阵行。具体来说功率平衡是等式约束每个时刻写一行。行里对应风电、光伏、电池放电、抽蓄发电、切负荷位置的系数是1对应电池充电、抽蓄抽水位置的系数是-1。等式右侧是负荷值。风光可用出力上限是不等式约束每个时刻两行。一行限制风电出力不超过wind_avail[t]一行限制光伏出力不超过pv_avail[t]。储能充放电功率上限是不等式约束。电池充、电池放、抽蓄抽、抽蓄发各一行分别限制功率不超过各自额定值。SOC转移方程是等式约束。电池和抽蓄各T个时刻写下来就是下一时刻存电量等于当前存电量加充电存入能量、减放电消耗能量。初始存电量和SOC上下限也是通过等式或不等式约束写入。构建矩阵时最容易出错的点是SOC转移方程的系数。这里我把公式展开一下以电池为例e_b[t1] e_b[t] eta_bc * p_bc[t] - p_bd[t] / eta_bd移项后-e_b[t] e_b[t1] - eta_bc * p_bc[t] p_bd[t] / eta_bd 0所以矩阵行里当前时刻存电量位置的系数是-1下一时刻存电量位置的系数是1电池充电功率位置的系数是-eta_bc电池放电功率位置的系数是1/eta_bd。同样的方法抽蓄的转移方程为e_p[t1] e_p[t] eta_pc * p_pc[t] - p_pd[t] / eta_pd初始存电量约束则直接写成一行等式变量位置系数为1等式右侧为初始存电量数值。3.2 完整可运行的Python代码这一节给出完整代码我按“数据-参数-变量索引-约束矩阵-求解-结果输出”的顺序组织。依赖库是numpy和scipy如果还没有安装先执行pip install numpy scipy完整代码如下import numpy as np from scipy.optimize import linprog # 1. 输入数据单位MW T 24 t np.arange(T) load ( 26.0 6.0 * np.sin(2 * np.pi * (t - 9) / 24) np.where((t 18) (t 22), 8.0, 0.0) np.where((t 7) (t 9), 5.0, 0.0) ) wind_avail np.clip(30.0 18.0 * np.cos(2 * np.pi * (t - 4) / 24), 5.0, 48.0) pv_avail np.clip(30.0 * np.exp(-((t - 12.5) ** 2) / (2 * 2.5 ** 2)), 0.0, 30.0) # 2. 储能参数 # 电池储能 E_b 100.0 # 容量 MWh P_b_max 50.0 # 额定功率 MW eta_bc 0.95 # 充电效率 eta_bd 0.95 # 放电效率 soc_b_min 0.2 * E_b soc_b_max 0.9 * E_b soc_b_0 0.5 * E_b # 废弃矿井抽水蓄能 E_p 160.0 P_p_max 20.0 eta_pc 0.85 eta_pd 0.85 soc_p_min 0.2 * E_p soc_p_max 0.9 * E_p soc_p_0 0.6 * E_p # 3. 目标函数系数 n_vars 9 * T c np.zeros(n_vars) c_w -80.0 # 风电并网收益 c_pv -80.0 # 光伏并网收益 c_bc 2.0 # 电池充电运维成本 c_bd 2.0 # 电池放电运维成本 c_pc 1.0 # 抽蓄抽水运维成本 c_pd 1.0 # 抽蓄发电运维成本 c_shed 10000.0 # 切负荷惩罚 c[0:T] c_w c[T:2*T] c_pv c[2*T:3*T] c_bc c[3*T:4*T] c_bd c[4*T:5*T] c_pc c[5*T:6*T] c_pd c[6*T:7*T] c_shed c[7*T:8*T] 0.0 c[8*T:9*T] 0.0 # 4. 构建约束矩阵 A_eq [] b_eq [] # 4.1 功率平衡每个时刻一个等式 for i in range(T): row np.zeros(n_vars) row[i] 1.0 # p_w row[T i] 1.0 # p_pv row[3*T i] 1.0 # p_bd row[5*T i] 1.0 # p_pd row[6*T i] 1.0 # p_shed row[2*T i] -1.0 # p_bc row[4*T i] -1.0 # p_pc A_eq.append(row) b_eq.append(load[i]) # 4.2 电池SOC转移方程 for i in range(T - 1): row np.zeros(n_vars) row[7*T i] -1.0 # -e_b[t] row[7*T i 1] 1.0 # e_b[t1] row[2*T i] -eta_bc # -eta_bc * p_bc[t] row[3*T i] 1.0 / eta_bd # p_bd[t] / eta_bd A_eq.append(row) b_eq.append(0.0) # 4.3 抽蓄SOC转移方程 for i in range(T - 1): row np.zeros(n_vars) row[8*T i] -1.0 row[8*T i 1] 1.0 row[4*T i] -eta_pc row[5*T i] 1.0 / eta_pd A_eq.append(row) b_eq.append(0.0) # 4.4 初始存电量 row np.zeros(n_vars) row[7*T] 1.0 A_eq.append(row) b_eq.append(soc_b_0) row np.zeros(n_vars) row[8*T] 1.0 A_eq.append(row) b_eq.append(soc_p_0) A_eq np.array(A_eq) b_eq np.array(b_eq) # 5. 构建不等式约束 A_ub [] b_ub [] # 5.1 风电、光伏出力上限 for i in range(T): row np.zeros(n_vars) row[i] 1.0 A_ub.append(row) b_ub.append(wind_avail[i]) row np.zeros(n_vars) row[T i] 1.0 A_ub.append(row) b_ub.append(pv_avail[i]) # 5.2 储能充放电功率上限 for i in range(T): row np.zeros(n_vars) row[2*T i] 1.0 A_ub.append(row) b_ub.append(P_b_max) row np.zeros(n_vars) row[3*T i] 1.0 A_ub.append(row) b_ub.append(P_b_max) row np.zeros(n_vars) row[4*T i] 1.0 A_ub.append(row) b_ub.append(P_p_max) row np.zeros(n_vars) row[5*T i] 1.0 A_ub.append(row) b_ub.append(P_p_max) # 5.3 SOC上下限 for i in range(T): # e_b soc_b_min -e_b -soc_b_min row np.zeros(n_vars) row[7*T i] -1.0 A_ub.append(row) b_ub.append(-soc_b_min) # e_b soc_b_max row np.zeros(n_vars) row[7*T i] 1.0 A_ub.append(row) b_ub.append(soc_b_max) row np.zeros(n_vars) row[8*T i] -1.0 A_ub.append(row) b_ub.append(-soc_p_min) row np.zeros(n_vars) row[8*T i] 1.0 A_ub.append(row) b_ub.append(soc_p_max) A_ub np.array(A_ub) b_ub np.array(b_ub) # 6. 求解 bounds [(0, None)] * n_vars res linprog( c, A_ubA_ub, b_ubb_ub, A_eqA_eq, b_eqb_eq, boundsbounds, methodhighs ) if not res.success: print(求解失败, res.message) exit() x res.x # 7. 结果提取 p_w x[0:T] p_pv x[T:2*T] p_bc x[2*T:3*T] p_bd x[3*T:4*T] p_pc x[4*T:5*T] p_pd x[5*T:6*T] p_shed x[6*T:7*T] e_b x[7*T:8*T] e_p x[8*T:9*T] # 打印关键时刻调度结果 print(时刻 负荷(MW) 风电(MW) 光伏(MW) 电池(MW) 抽蓄(MW) 切负荷(MW)) for i in range(T): bess_power p_bd[i] - p_bc[i] psh_power p_pd[i] - p_pc[i] print(f{i1:2d} {load[i]:6.2f} {p_w[i]:6.2f} {p_pv[i]:6.2f} f{bess_power:6.2f} {psh_power:6.2f} {p_shed[i]:6.2f}) # 新能源利用率 total_avail np.sum(wind_avail) np.sum(pv_avail) total_use np.sum(p_w) np.sum(p_pv) utilization_rate total_use / total_avail * 100 print(f\n新能源利用率: {utilization_rate:.2f}%) print(f切负荷总量: {np.sum(p_shed):.2f} MWh)这段代码里Etc_b和e_p的单位都是MWh所以SOC上下限直接写成了soc_b_min 0.2 * E_b。目标函数中c_w和c_pv是负值代表并网收益c_shed设成10000远大于其他所有系数量级确保求解器优先避免切负荷。关于同时充放电的问题这里做一点说明。模型中我没有添加“电池不能同时充电和放电”“抽蓄不能同时抽水和发电”这两类互斥约束原因是充放电都要计运维成本而且充放转换有能量损耗最优解天然不会同时做这两件互相抵消的事情。把互斥约束留到需要机组组合的整数模型里再处理对当前这个LP模型影响很小。3.3 求解结果如何导出成调度表跑完linprog后x向量里装的就是所有决策变量的最优值。从x里按索引区间切分出来再通过matplotlib画图或者直接输出成表格就能看到完整的一天调度曲线。如果想把结果保存成CSV方便后续分析只需加几行import pandas as pd df pd.DataFrame({ 负荷: load, 风电: p_w, 光伏: p_pv, 电池: p_bd - p_bc, 抽蓄: p_pd - p_pc }) df.to_csv(dispatch_result.csv, indexFalse)我习惯用“电池”和“抽蓄”两列直接存净功率正数表示放电/发电负数表示充电/抽水。这样画图时一眼就能看出储能什么时候充、什么时候放。4. 调度曲线解读两种储能如何接替工作4.1 典型出力曲线的三段式走势用上面那组示例数据跑出来的调度结果呈现出非常清晰的三段式结构。我把关键时段的调度行为拆开看。凌晨0点到6点风电出力处于全天高位30MW到45MW之间徘徊负荷只有22MW左右。此时光伏为零风电除了直接供负荷有十几MW的富余。这部分富余能量绝大部分被抽蓄吃掉抽水功率顶到20MW额定上限附近。电池也会分走一部分充电功率在10MW上下把电池从初值50MWh充向更高的存量。抽蓄吃额定功率、电池吃剩余富余这个分配方式是目标函数里运维成本差距自然推出来的抽蓄的单位运维成本低先可着它把功率用满。上午9点到下午15点光伏出力逐渐爬升中午达到近30MW峰值。负荷这时在28到32MW之间风电已经跌到15到20MW的区间。光伏除了带负荷还继续往电池里充一部分电。此时抽蓄基本不动作它的大容量电量要留给晚高峰。晚上18点到23点负荷冲上40MW以上光伏归零。这时风电只能提供20MW左右缺口超过20MW。调度结果里电池从傍晚开始放电功率逐步加大抽蓄在19点左右开始发电一直持续到23点。两个储能一起顶上晚高峰晚上系统不需要切负荷。这个结果很有代表性。它说明协调逻辑是抽蓄负责把凌晨的富余风电“寄存”到晚上电池负责在白天消化光伏富余和晚上快速响应负荷爬坡。 两种储能的节奏天然错开各自都在自己最合适的时段出力。4.2 关键指标新能源利用率、储能循环次数调度结果不能只看曲线还要算几个硬指标。我常用的指标有三个。第一个是新能源利用率。定义式是新能源利用率 (风电实际并网量 光伏实际并网量) / (风电可用发电量 光伏可用发电量) × 100%用代码计算就是sum(p_w) sum(p_pv)除以sum(wind_avail) sum(pv_avail)。如果这个值低于100%说明出现了弃风弃光。示例数据的利用率通常在85%到95%左右剩下的弃电主要出在风电大发的凌晨时段因为抽蓄容量有限、电池也存满了实在收不下那么多电。第二个是储能循环次数。粗略计算方法是电池放电总量除以电池额定容量。循环次数反映了储能设备的利用率也是寿命预测和经济性测算的基础数据。同样的方法也适用于抽蓄但抽蓄一般说“发电小时数”更合适比如一台20MW的抽蓄一天发电5小时就对应100MWh的放电量。第三个是切负荷总量。这个指标如果大于0说明系统装机或储能配置不足。示例数据下切负荷为0说明当前的储能容量足以支撑晚高峰缺口。把这几个指标和调度曲线放在一起看才能判断模型输出合不合理。如果利用率接近100%需要检查是不是储能容量拍得太富裕如果电池每天的循环次数超过两次就要考虑寿命约束是否太松。4.3 如果储能参数调整结果会怎么变调度模型最有价值的用途之一是做参数敏感性分析回答“储能装多大才够用”这类问题。我在项目中做过几组参数调整实验规律很清楚。电池容量从100MWh翻倍到200MWh凌晨富余风电充电的空间变大晚高峰电池可以支撑更久抽蓄的放电时长相应缩短。新能源利用率小幅上升但电池日均循环次数下降。这说明电池容量的边际效益在递减。抽蓄效率从85%降到75%凌晨同样是20MW抽水功率但真正存下来的能量变少了晚上能发出来的电量也随之减少弃电量上升晚高峰可能更依赖电池。这个实验直观说明效率对长时储能经济性的影响有多大。假如系统中只保留电池、去掉抽蓄凌晨风电富余时段电池只能存100MWh晚上负荷高峰到来前电池已经放空后半夜的尖峰时段只靠风电供不上系统出现切负荷。相反如果只保留抽蓄、去掉电池凌晨前4个小时抽蓄功率上限20MW导致存得慢白天光伏富余时段又缺乏快速充电手段晚高峰缺口依然存在。这两组对比实验体现的就是“互补”电池和抽蓄单独拿出来都有局限性放在一起才能覆盖不同时间尺度的调节需求。5. 我在做这个项目时踩过的坑5.1 时间索引错位SOC转移方程差一个下标第一次写SOC转移约束时我把电池存电量的等式下标弄错了写成e_b[i]和e_b[i-1]的关系。结果就是第0个小时的初始存电量没有被正确约束最后1个小时的存电量也没有归宿整个SOC曲线在末端莫名跳变。排查方法很笨但有效把求解出来的e_b序列打印出来人工核验每小时变化的量是否等于充放电能量乘以效率后的净值。如果发现某个时刻SOC跳变不符合转移方程十有八九是索引错位。经验是写SOC转移时从第1个小时开始算用当前时刻的充放电功率推算下一时刻的存电量循环范围是range(T - 1)而不是range(T)。最后一个时刻的存电量通过约束自然衔接不用为它单独写转移式。5.2 效率系数放反算出来“越充越多”这个坑非常隐蔽。刚开始我把电池充电效率放在分母上写成e_b[t1] e_b[t] p_bc[t] / eta_bc - p_bd[t] * eta_bd。结果充电100MWh反而存进去105MWh能量凭空多出来了5%。求解器一下子抓住这个“漏洞”拼命充电SOC顶着上限不松口结果自然完全不可用。效率系数方向的判断标准就一句能量从电网进储能一定有损耗转化后的量变小所以要乘以效率能量从储能回电网同样有损耗储能内部消耗的量变大所以要除以效率。5.3 变量顺序混乱导致约束矩阵对不上变量顺序不定清楚矩阵就没法填。我在一次修改中调整了变量排序但没有同步更新所有矩阵填充代码结果功率平衡约束里的p_bd索引指向了p_pc调度结果里抽蓄莫名其妙变成了电池数据错得毫无逻辑。后来我改成定义索引函数的方式避免硬编码数字def idx_pw(i): return i def idx_ppv(i): return T i def idx_bc(i): return 2*T i def idx_bd(i): return 3*T i def idx_pc(i): return 4*T i def idx_pd(i): return 5*T i def idx_shed(i): return 6*T i def idx_eb(i): return 7*T i def idx_ep(i): return 8*T i所有矩阵填充都调用这些函数。后续如果调整变量顺序只改这组函数即可约束矩阵代码一行都不用动。建议所有做优化建模的人都养成这个习惯。5.4 linprog收敛警告的排查方法运行linprog时偶尔会遇到res.success为False的情况。常见的status提示包括“The problem is infeasible”和“Numerical difficulties”。不可行问题通常出在约束自相矛盾。最容易出问题的是SOC上下限和初始存电量冲突。比如初始存电量设成0.1 * E_b又把SOC下限设成0.3 * E_b那么第0小时的约束必然冲突。排查时把所有等式约束和不等式约束分开打印逐条检查。数值困难则多半是量级差异过大。比如切负荷惩罚系数设成1e9而其他系数只有个位数矩阵条件数恶化求解器数值稳定性变差。建议把目标系数控制在10的4次方以内切负荷惩罚设成10000已经足够大。如果把单位统一为MW和MWh变量数值也不会出现百万级别的量差求解过程会稳得多。6. 这套代码的局限和后续扩展方向6.1 线性模型的简化边界坦诚地说这套LP模型做了不少简化它更像一个研究基座而不是一个可以直接上生产系统的调度程序。一是没有机组组合和启停逻辑。电池和抽蓄的充放电互斥约束没有显式建模解决的途径完全依赖目标函数中的成本差。二是没有网架潮流约束。这是典型的单母线模型默认所有节点在同一个电气点上不考虑线路潮流、电压约束和网损。三是没有考虑风光出力的随机性。我使用的是确定性时序把风电和光伏预测值当成真实值处理。四是负荷侧没有做需求响应全部作为刚性负荷处理。这些简化在系统级研究中是常见操作不影响理解互补调度的核心逻辑但写论文或者做工程时需要明确边界不能把简化模型的结论直接外推到真实电网。6.2 随机场景、滚动优化、MILP等扩展路径如果要把代码往论文方向扩展有两条最直接的路。第一条路是引入随机性。风电和光伏出力本质上都是随机过程可以生成多组误差场景把模型改成两阶段随机规划。第一阶段做储能日前计划第二阶段针对每个场景做实时再调度目标变成所有场景下的期望成本。这样算出来的调度方案比确定性模型皮实得多。第二条路是引入整数变量。比如电池充放互斥、抽蓄发电机组启动最小间隔、储能设备检修状态等都需要0-1变量。对应的求解器要从linprog切换到支持混合整数规划的工具比如PuLP配CBC求解器或者学术免费的Gurobi。模型规模变大后求解时间会明显上升但对真实工程场景来说这些约束基本都是必须的。还有一种更贴合工程实际的做法是滚动时域优化把一天的调度切成若干个4小时的窗口每个窗口只对未来4小时做详细优化每到下一个窗口再根据最新预测重新计算。这种模型预测控制的思路能在一定程度上吸收风光预测误差又不至于让模型复杂度爆炸。如果你刚接触这类项目我个人的建议是先别急着上随机模型和整数规划把我上面这版LP代码跑通把调度曲线画出来把“电池快充快放、抽蓄慢吞吞搬能量”这个时间尺度配合的直觉建立起来再去碰扩展模型。地基打牢了后面盖楼才稳。