ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

风光储互补调度Python实践:含废弃矿井抽水蓄能与电池协同优化

风光储互补调度Python实践:含废弃矿井抽水蓄能与电池协同优化 风电、光伏、储能、互补调度——这几个词放在一起其实就是一个“看天吃饭”的能源系统学会自己调度的故事。今天要分享的项目完整名称是《风电、光伏与储能含电池和废弃矿井小型抽水蓄能互补调度运行研究Python代码实现》一套使用Python搭建的能源优化调度代码。它解决的核心问题很直接在风机和光伏出力随机波动、电网容纳能力有限的前提下怎么安排电池和抽水蓄能的充放电节奏才能让弃风弃光最少、运行成本最低、供电最稳。无论你是做电力系统研究的学生还是刚接触新能源调度的工程师只要有一定的Python基础都能在这篇内容里找到可以直接复现的思路和代码骨架。这套研究里最让我感兴趣的点是“废弃矿井小型抽水蓄能”。常规抽水蓄能需要建上下两个水库受地理条件制约大但废弃矿井自带高差和巷道空间改造成本低还能盘活矿区闲置资源。它和电池储能在响应速度和容量上形成互补让整个调度系统更有层次。下面我就从问题拆解、数学模型、Python实现到排坑经验一层层把这块硬骨头啃明白。1. 项目整体思路与场景拆解1.1 为什么非要把风、光、储放在一起调度风电和光伏是天生的“看天吃饭”选手。风电出力夜间往往较大白天反而小光伏则恰好相反中午出力达到峰值夜间直接归零。这种日内互补特性看起来很美但真正并网时问题很大电力系统要求发电和用电实时平衡而风、光出力又是不可控的波动起来几分钟内就能飙升或骤降。如果电网调峰能力不足就会出现两个极端——要么弃风弃光要么出现功率缺口。这时候储能的价值就出来了。它像一个缓冲水池发电多了先存起来发电少了再放出来把时间维度上的错配给抹平。但储能本身也有优劣之分电池响应快、效率高但容量有限长时间大功率吞吐会加速老化抽水蓄能容量大、寿命长但响应速度慢启停也有时间成本。单靠任何一种储能都很难同时满足快速调节和长时间尺度的能量搬移需求。所以把电池和抽水蓄能放在同一个调度框架里让电池管短时高频的波动让抽水蓄能管长时大容量的削峰填谷这才是工程上真正合理的配置。这套代码做的正是这件事用一个统一的优化模型去协调这些不同特性的设备。1.2 电池储能与抽水蓄能的定位差异很多初学者容易把储能当成一个黑箱只关心“能存多少电”但实际调度时必须区分两类储能的运行特性。电池储能比如磷酸铁锂或者铅炭电池的特点是充放电功率大、响应快从零到满功率输出只需要毫秒到秒级非常适合做一次调频或平滑短时波动。但它也有硬伤循环寿命有限频繁深度充放电会加速容量衰减自放电率也不可忽视如果调度策略激进一个调度周期内可能损耗好几个百分点的能量。抽水蓄能则完全是另一套逻辑。它本质上是“水—电—水”的能量转换把电能转化为水的势能存储起来放水时再通过水轮机发电。它的优势是容量大、寿命长一个库容设计合理的小型抽蓄站等效储能容量可能达到几十兆瓦时甚至几百兆瓦时而且循环次数几乎不受限制。缺点是机组启停耗时较长一般从启动到满发可能需要几分钟到十几分钟水头变化还会影响发电效率。所以在调度模型里我不会把两种储能写成同样的约束而是让电池承担高频调节任务让抽蓄承担日尺度以上的能量搬移这样才能体现出互补的意义。1.3 废弃矿井怎么变成小型抽水蓄能电站把废弃矿井改造成抽水蓄能电站听起来像天方夜谭但原理上非常通顺。矿井内部有成体系的巷道、竖井和采空区天然存在竖直方向的高差。建设时利用上部巷道或地面场地作为上水库利用底部采空区或相邻巷道作为下水库中间通过原有井筒布置压力管道和水轮机组。用电富余时把水从下水库抽到上水库用电紧张时放水发电。相比新建常规抽水蓄能电站它最大的优势有两个一是无需新建大规模水库不占用额外土地造价大幅降低二是矿区基础设施供电线路、交通、厂房可以复用建设周期短。当然这种方案也有需要特殊处理的技术细节。矿井水下渗和围岩稳定性是首要考虑的安全因素必须做防渗处理和结构加固采空区的水质可能偏酸性或含有金属离子对水泵水轮机有腐蚀风险。不过在调度研究层面我们可以简化处理把上下水库看作一个具有容量上下限和能量转换效率的储能单元重点考虑它的功率约束、容量约束和效率参数。这样既贴合工程实际又不会让模型复杂到无法求解。2. 数学模型目标函数与约束条件拆解2.1 目标函数怎么设计才合理调度模型的核心是目标函数它决定了优化方向。我见过不少项目一上来就把“成本最小化”写在目标里却忽略了不同设备在成本结构上的差异结果模型解出来的策略并不符合实际运行需求。在这套代码里我采用的是“运行成本弃电惩罚储能损耗”的加权最小化结构。运行成本这部分主要考虑向电网购电的费用如果系统与大电网相连以及风电、光伏的运行维护成本。弃电惩罚是一个关键项当风电和光伏出力超过负荷需求时如果不允许弃电模型可能会强制储能以超出额定功率的方式充电这显然不现实如果弃电没有惩罚模型又会倾向于无成本地削减可再生能源出力失去调度的意义。所以我把弃电惩罚系数设为一个较大的数值让模型在“弃电”和“调节储能”之间优先选择后者只有在储能能力确实耗尽时才允许弃电。储能损耗项用于反映电池充放电循环对寿命的折损我用一个与充放电功率平方相关的惩罚系数来表示它能让模型尽量避免过于频繁的深度充放电。这个目标函数设计的关键在于权重系数的比例。惩罚系数如果过高模型会过度保守储能设备的利用率偏低惩罚系数如果过低模型又可能为了多用储能而频繁充放牺牲电池寿命。我在实际调试中发现弃电惩罚系数取购电电价的3到5倍储能损耗系数取一个很小的正数比如0.001就能得到比较均衡的调度结果。2.2 约束条件逐条拆解目标函数只是回答“往哪个方向优化”真正的约束条件才是模型能够落地运行的关键。我按以下几条来组织约束第一条是功率平衡约束也就是任意时刻所有电源出力加上储能放电、减去储能充电必须等于负荷需求如果并网还包括与电网的交换功率。这条约束是电网安全运行的红线绝对不能违反。第二条是各设备出力上下限约束风机、光伏的出力不能超过当前时刻的预测可用功率也不能低于零电池和抽蓄的充放电功率要受额定功率限制。第三条是储能系统的动态约束电池的荷电状态SOC按“上一时刻SOC 充电能量 - 放电能量 / 充放电效率”递推抽水蓄能的上水库水量同理但水量和电量的换算要考虑水头和机组效率。第四条是充放电状态互斥约束——在同一时刻储能装置不能同时充电和放电否则模型会出现“一边充一边放”的荒谬结果白白损耗能量。这个约束要用0-1变量配合大M法来实现。第五条是调度周期末端的储能SOC约束通常要求调度周期结束时SOC回到初始值附近这样第二天才能继续按同样的策略运行。其中最容易出问题的是SOC递推约束。它涉及相邻两个时段的耦合如果写成非线性等式会让求解难度陡增。我的处理方式是把它拆成两个不等式电池的SOC增量不超过充电能量乘以充电效率也不超过放电能量除以放电效率再通过状态互斥约束保证同一时刻只有一个方向有能量流动。这样既保持了物理意义又让模型保持在线性规划的框架内。2.3 这些约束里的非线性项怎么处理电力系统优化中最常见的非线性来源有三个一是功率与效率的乘积关系二是充电放电状态互斥三是分段成本函数。前两个可以用0-1变量和大M法解决第三个可以分段线性化。拿状态互斥来说假设电池的充电状态用二值变量u_ch表示放电状态用u_dis表示物理上要求u_ch u_dis ≤ 1即可。但光有这个还不够因为充电功率P_ch和放电功率P_dis都还是连续变量模型仍然可能让P_ch和P_dis同时取正值然后再让它们分别乘以各自的效率再叠加进SOC递推式。所以我需要再写两条约束充电功率上限受u_ch控制P_ch ≤ P_ch_max × u_ch放电功率上限受u_dis控制P_dis ≤ P_dis_max × u_dis。这样一来只要u_ch为0充电功率就必须为0互斥就真正成立了。这个“二值变量锁连续变量”的思路是整个MILP模型里最关键的一步。分段线性化的思路也类似。如果电池的寿命损耗成本是充电深度的分段函数我不会直接构造非线性表达式而是用一组二进制变量确定当前充电深度落在哪个分段区间再用该区间的线性斜率计算成本。这个技巧在学术论文里叫“分段线性近似”在工程代码里其实就是多写几条约束但效果立竿见影模型依然是线性混合整数规划求解器可以高效处理。3. Python实现环境准备与数据工程3.1 工具链选型为什么是pulp加pandasPython做优化调度可选的工具不少有付费的Gurobi、CPLEX有开源的CBC、GLPK也有更偏学术的Pyomo。我在这套代码里用的是pulp加CBC求解器原因很朴素——开源免费、pip直接安装、API简单直观。pulp的建模语法非常接近数学表达式像我这样从论文公式“翻译”成代码几乎不会遇到理解障碍。对于研究规模的问题比如168个小时的调度周期、15分钟一个时段CBC求解器的速度完全够用通常几秒到几十秒就能收敛到最优解。如果你的问题规模特别大pulp也支持无缝切换求解器把CBC换成Gurobi只需要改一行代码这是它最方便的地方。数据处理方面我用pandas做主力。风电和光伏的出力预测数据、负荷数据通常以CSV或Excel给出pandas的read_csv和DataFrame操作能轻松完成清洗、对齐和重采样。时间索引的处理尤其重要——不同数据源的采样间隔可能不同有的15分钟一个点有的1小时一个点必须统一重采样后再喂给模型。另外numpy用来做数组运算和随机数生成后续做蒙特卡洛场景生成时会用到matplotlib用来画调度结果曲线。安装环境也很简单一个requirements.txt就能搞定pandas、numpy、matplotlib、pulp。不需要装额外的求解器pulp会自带CBC的可执行文件开箱即用。这点对新手特别友好踩坑的概率大大降低。3.2 数据获取与数据清洗的那些坑做真实调度研究数据永远是最耗时间的环节。我常用的是欧洲ENTSO-E开放数据和国内某些公开数据集格式一般是CSV包含时间戳、地区、风电出力MW、光伏出力MW、负荷MW等字段。拿到数据后我做的第一件事永远是画时序图用眼睛检查有没有明显的异常值。比如风电出力出现负值多半是数据单位或符号约定有问题负荷在凌晨突然变成零可能是原始数据缺测。这些异常值如果直接喂给模型轻则结果不可信重则求解器报不可行。清洗策略上我会对缺失值做线性插值对超出合理范围比如风电出力大于装机容量的数据做截断处理再用一个滚动中位数过滤器剔除毛刺。另一个容易踩坑的地方是数据结构设计。我习惯把所有数据合并成一个长表列名分别是“time”、“wind”、“pv”、“load”时间列统一转换成pandas的DatetimeIndex并按15分钟或1小时间隔重采样。重采样时要注意聚合方式风力和光伏出力取平均值负荷也取平均值不要用求和因为功率的单位是MW时间段的平均值才代表该时段的典型出力水平。数据准备好之后我会额外绘制一个“风光出力与负荷的日特性曲线”图直观观察三类曲线的峰谷关系这对接下来的调度策略设计非常有帮助。3.3 核心数据结构从DataFrame到优化变量的映射pandas负责数据管理但优化模型需要的是“决策变量”。我的做法是用字典结构来组织决策变量以时段t为键每个键对应一个LpVariable对象。比如P_bat_ch[t]表示t时段电池的充电功率SOC_bat[t]表示t时段末电池的荷电状态。把所有变量按类型分组存进不同的字典模型求解后取数值时也按同样的字典结构去读逻辑非常清晰。这里有个小技巧LpVariable.dicts函数支持批量生成变量列表。比如soc LpVariable.dicts(SOC_bat, T, lowBound 0.1, upBound 0.9)一行代码就能生成从t0到tT-1的所有SOC变量而且自动带上SOC_bat_0、SOC_bat_1这样的名字方便调试时输出分析。同样的方式也可以批量生成二进制变量只需要指定cat Binary。这个API设计对代码的可读性和可维护性帮助极大也是我推荐pulp的原因之一。数据从DataFrame到优化变量的映射核心是“时段数T”这个参数。我通常把调度周期取为24小时或168小时时段间隔为1小时。T越大模型越精细但变量数量和约束数量线性增长求解时间相应增加。对于研究验证24小时单日调度是最常见的起点如果要研究季节性特征就取168小时的周调度但需要给模型更多的求解时间。4. 核心代码实现与调度逻辑4.1 参数设置与数据读入这部分是整个代码的地基。我会把所有物理参数集中放在一个config字典里包括风电装机容量、光伏装机容量、负荷峰值、电池额定功率、电池容量、充放电效率、抽蓄额定功率、抽蓄等效容量、抽蓄效率、初始SOC、末态SOC要求等。这样做的最大好处是后续做敏感性分析时不需要改模型代码只需要改config字典就能跑不同场景。数据读入的代码逻辑是读CSV清洗重采样然后截取要研究的时段。如果数据长度不足一个调度周期我会用“最后一天重复”或“线性外推”的方式补齐。实际处理中我更推荐保留原始长序列然后在构建模型时用切片的方法选取研究窗口这样反复调参时不需要反复读文件。4.2 优化模型的构建与求解先上一段核心代码框架展示如何用pulp建立和求解这个混合整数线性规划模型import pandas as pd import numpy as np import pulp as pl # 读取和预处理数据示例 df pd.read_csv(wind_pv_load.csv, parse_dates[time]) df df.set_index(time).resample(1H).mean().dropna() T len(df) t range(T) # 参数配置 config { P_wind_max: df[wind].max() 10, # 含一点裕度 P_pv_max: df[pv].max() 5, P_bat: 30, # 电池额定功率 MW E_bat: 120, # 电池容量 MWh eta_bat_ch: 0.95, eta_bat_dis: 0.95, P_phs: 25, # 抽蓄机组额定功率 MW E_phs: 200, # 抽蓄等效容量 MWh eta_phs: 0.75, # 抽蓄综合效率 soc_bat_init: 0.5, soc_phs_init: 0.5, } # 创建模型 model pl.LpProblem(Hybrid_Energy_Scheduling, pl.LpMinimize) # 决策变量 wind_curt pl.LpVariable.dicts(wind_curt, t, lowBound0) pv_curt pl.LpVariable.dicts(pv_curt, t, lowBound0) bat_ch pl.LpVariable.dicts(bat_ch, t, lowBound0, upBoundconfig[P_bat]) bat_dis pl.LpVariable.dicts(bat_dis, t, lowBound0, upBoundconfig[P_bat]) phs_gen pl.LpVariable.dicts(phs_gen, t, lowBound0, upBoundconfig[P_phs]) phs_pump pl.LpVariable.dicts(phs_pump, t, lowBound0, upBoundconfig[P_phs]) soc_bat pl.LpVariable.dicts(soc_bat, t, lowBound0.1, upBound0.9) soc_phs pl.LpVariable.dicts(soc_phs, t, lowBound0.2, upBound0.95) # 0-1状态变量 u_bat_ch pl.LpVariable.dicts(u_bat_ch, t, catBinary) u_bat_dis pl.LpVariable.dicts(u_bat_dis, t, catBinary) u_phs_gen pl.LpVariable.dicts(u_phs_gen, t, catBinary) u_phs_pump pl.LpVariable.dicts(u_phs_pump, t, catBinary) # 目标函数购电成本 弃电惩罚 储能损耗 # 假设当前场景是孤网运行所以购电项为0以弃电惩罚和损耗为主 penalty_curt 100.0 loss_coeff 0.001 model ( pl.lpSum(penalty_curt * (wind_curt[i] pv_curt[i]) loss_coeff * (bat_ch[i] bat_dis[i] phs_gen[i] phs_pump[i]) for i in t) ) # 约束功率平衡孤网负荷 风电 光伏 储放 - 储充 - 弃电 for i in t: model (df[wind].iloc[i] - wind_curt[i] df[pv].iloc[i] - pv_curt[i] bat_dis[i] - bat_ch[i] phs_gen[i] - phs_pump[i] df[load].iloc[i], fpower_balance_{i}) # 约束风电、光伏弃电上限 for i in t: model wind_curt[i] df[wind].iloc[i] model pv_curt[i] df[pv].iloc[i] # 约束电池SOC递推 for i in t: if i 0: model soc_bat[i] config[soc_bat_init] (bat_ch[i] * config[eta_bat_ch] - bat_dis[i] / config[eta_bat_dis]) / config[E_bat] else: model soc_bat[i] soc_bat[i-1] (bat_ch[i] * config[eta_bat_ch] - bat_dis[i] / config[eta_bat_dis]) / config[E_bat] # 约束抽水蓄能SOC递推等效电量 for i in t: if i 0: model soc_phs[i] config[soc_phs_init] (phs_pump[i] * config[eta_phs] - phs_gen[i] / config[eta_phs]) / config[E_phs] else: model soc_phs[i] soc_phs[i-1] (phs_pump[i] * config[eta_phs] - phs_gen[i] / config[eta_phs]) / config[E_phs] # 约束储能充放电状态互斥 for i in t: model bat_ch[i] config[P_bat] * u_bat_ch[i] model bat_dis[i] config[P_bat] * u_bat_dis[i] model u_bat_ch[i] u_bat_dis[i] 1 model phs_pump[i] config[P_phs] * u_phs_pump[i] model phs_gen[i] config[P_phs] * u_phs_gen[i] model u_phs_pump[i] u_phs_gen[i] 1 # 末态约束SOC回到初始值 model soc_bat[T-1] config[soc_bat_init] model soc_phs[T-1] config[soc_phs_init] # 求解 solver pl.PULP_CBC_CMD(msgFalse, timeLimit120) result model.solve(solver) print(求解状态:, pl.LpStatus[model.status]) print(目标函数值:, pl.value(model.objective))这段代码虽然是一个简化版但已经把“功率平衡、SOC递推、状态互斥、末态约束”四大核心要素全部体现出来了。实际研究项目里你可能还要增加线路潮流约束、备用容量约束、机组启停时间约束等但骨架是完全一样的。求解完成后取变量值也是一门学问。我习惯先判断model.status是不是Optimal如果是再把各变量的value逐个取出来组装成DataFrame方便后续分析和画图。对于MILP模型如果求解器没有找到最优解而是停留在某个可行解你也应该把当前解输出来看——很多时候它已经足够接近最优。CBC求解这类小规模MILP通常没有任何压力但如果求解时间超过两三分钟建议检查一下是不是约束写错了、变量边界设置不合理或者数据量确实太大。4.3 结果可视化与调度曲线解读调度的输出结果如果没有可视化等于白算。我一般画四张图放在一起组成一个2×2的figure第一张是风电与光伏的出力曲线柱状堆叠标注出被弃掉的部分和实际利用的部分第二张是储能设备的充放电功率曲线电池一条线、抽蓄一条线正值为放电负值为充电第三张是SOC曲线两条线左轴电池SOC右轴抽蓄SOC第四张是负荷平衡曲线负荷、总供给、储能贡献叠加。调度结果怎么看比怎么看代码更重要。一个合理的调度方案从图上应该能读出三个信息一是系统是否出现了明显的弃电如果弃电集中在中午光伏高峰时段说明储能容量不足以平移这部分能量接下来应该考虑扩容电池或抽蓄二是电池的充放电切换是否过于频繁如果SOC曲线像锯齿一样密集振动说明目标函数里对储能损耗的惩罚太弱模型在用电池“刷”平衡三是末态SOC是否回到初始值附近如果偏差过大说明约束写错了或者模型压根没满足末态约束。我调参时90%的时间都在盯着这四张图找规律而不是看求解器的日志输出。5. 常见问题与调试经验实录5.1 求解器报“不可行”怎么办这是最打击新手的错误几乎每个人都遇到过。模型不可行说明你写的约束之间互相矛盾不存在任何一组变量取值能同时满足所有条件。我在调试时有一个固定的排查顺序先检查功率平衡约束。最常见的问题是负荷数据有零值或负值而约束要求负荷等于所有电源出力之和这就把可行域直接压缩没了。遇到这种情况我把负荷小于0的记录替换成0或者检查是不是单位换算出了问题kW和MW混用是经典错误。再检查SOC递推约束。如果在某个时段电池的初始SOC加上最大充电量减去最小放电量之后超出了0到1的范围模型就会不可行。这时候需要检查SOC的上下限设置是否合理以及末态约束和初始SOC之间的兼容性。最后检查状态互斥约束。有时候我为了图省事只写了u_ch u_dis ≤ 1但忘了写P_ch ≤ P_ch_max × u_ch那模型依然可以让功率取非零值虽然不会不可行但结果必然怪。最有效的排查工具其实是pulp自带的模型导出功能。设置model.writeLP(debug.lp)用文本编辑器打开这个LP文件把约束一条条读一遍通常几分钟之内就能定位到矛盾点。这个方法我认为比任何printf调试都高效。5.2 电池SOC数值波动异常的分析有一次模型求解正常结果却显示电池SOC在下半夜无缘无故上升白天负荷高峰反而放电很少。检查后发现原因出在充电效率参数写反了我把充电效率同时应用在“充电时存入的能量”和“放电时消耗的SOC”上等于系统里的能量凭空多出来一部分模型当然会钻这个空子在低谷电价时段疯狂充电然后高SOC值虚高。正确的写法是充电时SOC增量等于充电功率乘以充电效率放电时SOC减量等于放电功率除以放电效率两处效率参数必须分开写。类似的坑也出现在抽水蓄能上抽水耗电量和发电电量之间不是同一个效率值发电工况还要考虑水轮机效率随水头变化的因素。虽然研究模型可以简化成固定综合效率但至少在参数设置上要区分充放电方向避免出现“永动机”式的循环增益。5.3 时间尺度与求解速度的权衡调度周期取24小时、以15分钟为时段间隔会产生96个时段如果取168小时周调度、1小时间隔只有168个时段。变量数量差异带来的是求解时间的天壤之别。我实测过CBC求解一个96个时段的MILP模型通常只需要几秒到十几秒但如果是168个时段且加入了机组启停约束可能几分钟都算不完。如果你的研究需要长时间尺度的仿真我的建议是“分层调度”先以1小时为间隔跑一个周调度把每小时的储能SOC目标值算出来再以15分钟为间隔跑日内滚动优化把SOC目标作为硬约束嵌入进去。这种两阶段策略既保证了计算速度又不丢失短时波动的细节是工程上常见的做法。6. 从确定性调度到更贴近实际的扩展方向6.1 考虑风光出力预测误差的鲁棒优化前面介绍的是一个确定性模型假设风电、光伏出力在调度前是已知的常数。但真实运行中预测误差是常态天气预报说下午三点风速9米/秒实际可能是12米/秒或者6米/秒。应对预测误差最简单的扩展是做“滚动时域优化”和“场景树随机规划”。滚动时域的思路很好理解每15分钟重新读取最新的预测数据重新求解未来4小时的调度计划但只执行第一个时段的指令。这种“边走边看”的策略能持续修正误差工程应用非常广泛。随机规划则是把预测误差的概率特征纳入模型生成10个或50个可能的风光出力场景让目标函数变成所有场景下的期望成本约束条件在每个场景下都必须满足。这样求得的调度策略比单一确定性场景稳健得多。代价是变量和约束数量成倍增加模型更大但现代求解器配合场景缩减技术比如k-means聚类选出代表性场景完全扛得住。6.2 把废弃矿井抽蓄的地理约束写进模型我之前把抽水蓄能简化成了一个“带效率的电池”但在真正的工程预可研阶段这个简化是不够的。矿井型抽蓄有几个特殊约束一是上下水库的库容曲线往往不是线性的水位越高单位水头对应的储能电量越多因此SOC和可发电量之间不是线性关系二是井下巷道的水位波动会影响水泵的最小淹没深度低水位时机组可能无法启动三是矿山的沉降和渗漏风险可能要求水库不得在某个水位区间长时间停留。把这些因素写成约束并不复杂关键是把库容—水位曲线分段线性化再把“禁止停留区间”表示成一组0-1变量约束。加入这些细节之后模型才算真正“长在”了废弃矿井这个物理场景上。6.3 我的几点实操体会最后分享几条个人经验。第一不要一上来就追求模型的完美先把最基本的功率平衡加电池约束跑通拿到一组哪怕很粗糙的调度曲线再逐步加抽蓄、加状态互斥、加末态约束。每加一类约束就画一次图检查物理意义这样能最快定位“是模型错了还是代码错了”。第二参数设置时养成写注释的习惯。我吃过亏一个效率参数忘记注释方向三天后再跑代码完全不知道当初怎么想的白白浪费一个下午。第三如果你提交论文或者报告一定要附上调度结果的时序图而不是只写优化目标值——审稿人和导师看图的直觉比看数字快得多一张漂亮的调度曲线比两千字说明都更有说服力。这套风电、光伏和储能互补调度的Python代码本质上是一个“搭积木”的框架把每种电源和储能都抽象成一组变量和约束然后交给优化求解器去找最佳组合。如果你把风电、光伏替换成其他分布式电源把抽蓄替换成压缩空气储能模型骨架几乎可以原封不动地复用。这也是我当初选择用Python做这套研究的直接原因——它的生态足够丰富建模足够灵活让一个研究想法能以最快的速度变成一条可以反复调试、验证和扩展的代码流水线。
返回列表