前几天有个做新能源方向的研究生朋友跟我聊,说高比例可再生能源并网之后,系统的"钱包"和"脾气"越来越难兼顾——这边光伏风电出力一波动,那边就急需爬坡和调频资源;可要配置储能来兜底,成本又高得吓人。他卡在一个问题上:文献里那些虚拟电厂多时间尺度调度模型,到底是怎么把灵活性和储能成本放进同一个优化框架里的?储能衰减又不是简单按年限折旧,怎么建模才能让Matlab程序既收敛又贴近工程实际?
这篇文章我就把这套框架完整拆一遍。内容围绕虚拟电厂(VPP)如何利用多时间尺度调度平衡系统灵活性与储能全生命周期成本展开,包括日前-日内滚动决策的耦合逻辑、储能衰减建模的数学化方法、目标函数与约束体系的构建,以及Matlab+YALMIP求解时的代码写法和实战踩坑记录。适合正在做虚拟电厂、储能调度、高比例新能源并网方向研究的研究生和工程师参考。
1. VPP的资源聚合与灵活性本质:为什么分布式资源必须协同调度
1.1 高比例可再生能源带来的净负荷爬坡难题
先看物理层面。当风电、光伏装机占比提升以后,电力系统面临的头号问题并不是"电量不够",而是"电量时多时少、爬坡需求剧烈"。业内常说的净负荷曲线,就是用户用电负荷减去风光出力之后剩下的那部分。这条曲线在光伏大发的中午会被压得很低,傍晚光伏骤减而负荷又进入晚高峰时,会突然向上陡峭爬升。有人把这条曲线形象的叫做"鸭子曲线",鸭子脖子那段就是系统最难受的地方:常规机组需要在几个小时内快速增加大量出力,这需要的是调节能力,也就是灵活性。
这种爬坡需求在数字上非常直观。假设一个区域晚高峰净负荷爬坡率达到每小时800 MW,而区域内火电机组总爬坡能力只有600 MW/小时,剩下200 MW的缺口谁来补?要么拉闸限电,要么让储能顶着,要么从区域外购电。虚拟电厂的第一个价值就在这里:它把散落在配网侧的储能电站、可控负荷、分布式光伏和分散风电聚合起来,把原本调度员看不见的资源变成一块可统一调度的"虚拟机组"。
1.2 虚拟电厂的三类资源画像与调度角色
虚拟电厂内部资源虽然五花八门,但从调度建模角度看,基本可以归类为这三种角色,每个角色在优化模型里的数学形态完全不同。
第一类是出力不可精确控制的分布式电源,典型代表是分布式光伏和分散式风电。它们的出力由气象条件决定,调度模型只能把它们当作"预测值+预测误差区间",通过弃风弃光来向下调节,但没法让它们超发。在Matlab代码里通常表现为一组给定出力序列 P_pv(t) 和 P_wt(t),模型只需要决策"弃多少"即可。
第二类是双向可控的储能系统,这是虚拟电厂灵活性的核心来源。储能既能充电也能放电,响应速度以毫秒到秒计,但代价是充放电过程存在能量损失,而且每充放一次都会消耗循环寿命。在约束里需要同时描述功率限制、SOC递推关系、充放电状态互斥,在目标函数里还要计入运维成本和衰减成本。
第三类是柔性负荷,包括可削减负荷、可转移负荷和可平移负荷。典型例子是空调、电动汽车充电桩、工业电解槽。它们的特点是"不是非得在那个小时用满功率",但用不满的部分需要用户在舒适度或生产流程上让步,因此模型里会设置调节补偿成本。
我在这类复现项目里通常设定一个微观案例:虚拟电厂由200 MW分散风电、100 MW分布式光伏、50 MW/200 MWh电化学储能、200 MW可调常规机组和30 MW柔性负荷组成。这个规模足够跑出有说服力的调度结果,又不会让Matlab求解时间膨胀到难以接受。
1.3 储能的多重身份:能量时移、爬坡支撑与备用容量
储能之所以在虚拟电厂里地位特殊,是因为它在不同时间尺度上扮演不同角色。在小时级尺度上,它做能量时移——光伏大发时充电,晚间高峰时放电,赚取峰谷电价差;在分钟级尺度上,它做爬坡支撑——常规机组爬坡跟不上时,储能先顶上那几分钟;在秒级尺度上,它做频率响应——但这一层通常不由日前调度模型直接决策,而是由实时控制器执行。
这就引出一个关键认知:储能不是"一块电池",而是一个跨时间尺度耦合的状态变量。它的SOC从早上到晚上一直在传递,任何一层调度决策都受上一层SOC结果约束,同时又给下一层留下边界条件。多时间尺度调度框架存在的根本原因,就是不同资源对不同时间粒度问题的响应能力天差地别,必须分层决策才既高效又可靠。
2. 多时间尺度框架的物理基础与接口变量:今天定什么,此刻又定什么
2.1 日前计划的决策边界与市场互动
多时间尺度调度通常至少分三层建模,最上层是日前计划。这一层的决策依据是未来24小时的风光出力预测、负荷预测和分时电价,时间分辨率一般为1小时。模型要回答的问题是:明天每个小时里,机组开还是不开、开多少功率,储能大体上按什么充放电轨迹运行,虚拟电厂和外部电网的联络线购售电计划怎么定。
这层决策最核心的特点是"粗颗粒、强约束、面向市场"。例如储能SOC不需要精确到每分钟,但需要保证一天结束时SOC回到初始值,这样下一天才有同样的调节空间;常规机组需要预留启停时间和上下爬坡能力,否则日内执行时才发现调不动就晚了。日前计划给出的其实是一个参考基线,让虚拟电厂经营主体能够提前申报购电计划、签订日前市场合约。
这里有个值得注意的设计:日前边界条件和实时执行能力之间必然存在偏差,因为预测不可能百分之百准确。所以多时间尺度框架的本质是一套"滚动修正"机制,而不是一个单次求解的大规模优化问题。
2.2 日内滚动优化的模型预测控制逻辑
日内滚动层是这套机制的核心修正环节。它的时间窗口一般是未来4到6小时,分辨率缩短到15分钟或5分钟,每15分钟重新滚动求解一次。每次求解时,把最新的超短期预测数据、最新的实测状态、上一时段已经执行的实际值全部刷新,然后优化未来几个时段的控制序列——但只执行第一个时段,等到下一个滚动周期再重新优化。
这个思路和工业过程控制里的模型预测控制(MPC)是同一个底层逻辑:预测、优化、反馈、再预测。每个滚动周期里,当前实测SOC是最新优化问题的初值约束,天气预报更新后的风光出力是最新输入条件。这样,日前计划里没预料到的云层遮挡、风速突变、负荷尖峰,都能在日内层被及时修正。
在Matlab代码实现上,日内滚动不是简单跑一次优化,而是要写一个循环:在每个时间断面上更新数据、调用优化求解、取出第一个动作、下发执行、然后推进到下一个断面。我曾经见过有人把日内滚动实现成"把每个时段都单独求解一次独立优化",结果SOC状态完全断裂,储能相当于每次都从零开始决策,调度结果自然差得离谱。这种坑后面我会用专门篇幅细说。
2.3 跨尺度状态传递的核心变量:SOC与联络线计划
不同时间尺度之间的信息传递,不是把全部决策变量都搬过去,而是通过几个关键"接口变量"衔接。第一是储能SOC。日前模型算完最后一小时SOC的期望轨迹,日内模型拿当前实际SOC做初值;日内模型给实时控制器下发指令时,也要告诉它目标SOC区间,否则实时控制器只知道"现在要不要充放电"却不知道"该把电量维持在哪一档",很容易让电池在低SOC附近徘徊,关键时刻无电可放。
第二是联络线功率计划。日前层已经和市场约定好了每小时的购售电量,日内层除非偏差大到严重影响安全,一般不会大幅突破联络线限额,否则会产生市场惩罚。所以在日内模型的约束中,联络线功率通常被限定在日前计划值的某个偏差带内,例如正负10%。
第三是常规机组的基点功率。日前层定了机组经济运行点,日内层在机组爬坡速率范围内做微调,但不会随意启停,因为机组启停决策的时间尺度远大于日内滚动周期。
理解了这些接口变量,多时间尺度调度模型的结构就清楚了:三层决策、层层嵌套,每层决策变量不同、约束边界不同、目标函数侧重也不同。
3. 储能衰减建模的折现逻辑:只算初始投资等于高估电池经济性
3.1 锂电池老化的物理过程与寿命损耗度量
很多初学者做储能调度时,只把储能当作一个有容量上限、功率上限和效率的"理想水库",充放电成本只算电费损耗。这种处理在做短期分析时勉强说得过去,但一旦模型要回答"该配置多少储能""储能该不该在某个时刻多充放一点"这类涉及经济性比较的问题,就会得出明显偏激进的结论:既然充放电不花什么钱,那系统当然会倾向于让储能频繁大功率充放,反正求解器感觉不到电池在加速老化。
电池老化的物理过程很复杂,涉及SEI膜增长、锂离子损失、活性材料结构坍塌等,但对调度优化来说,我们真正需要度量的是一个宏观指标:电池在某个充放电循环中消耗了多少"寿命"。工程上最常用的度量方式是循环次数与放电深度的关系。同一块电池,如果每次都深度放电到20% SOC再充满,循环寿命可能只有1200次;如果只浅充浅放、每次只动用20%的容量区间,循环寿命能翻两三倍。这意味着电池的每一次充放电行为,都应该被折算成对应的一笔寿命损耗成本。
3.2 从循环寿命曲线到单次充放电成本投影
在Matlab优化模型里嵌入衰减成本,最实用的做法是构建一个"等效循环损耗系数"。具体分三步:
第一步,拿到电池厂家提供的循环寿命数据表,通常是"放电深度-循环次数"对照关系。第二步,拟合出循环次数 N 与放电深度 DOD 的关系曲线,例如半经验形式 N(DOD) = a * DOD^(-b),其中a和b是电池特性参数。第三步,把电池全寿命总吞吐电量近似为 Q_total = N(DOD) * DOD * E_rated,然后用电池初始投资成本除以总吞吐电量,得到单位吞吐电量成本。
举个例子就很直观:一块额定容量为1 MWh的电池,初始投资200万元,按厂家数据在DOD=80%条件下循环次数为4000次,那么总吞吐电量是4000 × 0.8 × 1 = 3200 MWh,单位吞吐电量的损耗成本就是200万 / 3200 = 625元/MWh。也就是说,这块电池每充放1 MWh,就相当于消耗了625元的寿命价值。把这个数值放进目标函数里,充放电行为就不再"免费"了。
这种"吞吐电量折算成本"的方法虽然工程化程度高、模型简单,但在精度上有限:它对DOD不同的循环用了同一个平均成本,而实际上浅循环的单位损耗成本通常更低。更精细的做法是做分段线性化:把DOD区间拆成几段,每段取不同的损耗系数,优化时通过分段线性约束判断当前充放电对应哪个DOD区间。这个做法有效但代码量会增加不少,后面我会给出实现建议。
3.3 衰减系数对调度结果影响的直观验证
衰减成本加进目标函数真的能改变调度行为吗?我用同一个案例做过对比测试。不加衰减成本时,优化结果中储能充电功率经常贴着上限走,一天下来循环次数折算相当于满充满放3.5次,电池等效寿命只有约4.7年。加入衰减成本后,储能不再频繁深度充放,一天折算循环次数降到1.8次,电池等效寿命延长到约8.9年,系统总运行成本反而低了。因为虽然少了峰谷套利收益,但电池更换成本的大幅节省把这些收益抵消了还有富余。
这正是衰减建模在整个虚拟电厂调度模型里最核心的价值:它不是给模型增加了一个无关紧要的细节项,而是直接改变了最优解的方向。如果说灵活性约束决定了储能"能不能动",衰减成本决定了储能"该不该动、动多少、怎么动"。
4. 目标函数与约束体系的数学表达:灵活性和成本如何在一个模型里谈价
4.1 成本项拆解与弃电惩罚的标尺作用
把调度问题写成优化模型,第一步是把所有矛盾放进同一个目标函数里,让求解器去"谈价"。典型的目标函数是总运行成本最小化,包含下面几类成本:
- 购电成本:虚拟电厂从外部电网买电的费用,按分时电价结算,鼓励在低谷时段购电。
- 售电收益:虚拟电厂向外部电网卖电的收入,出现在目标函数里时符号为负。
- 常规机组燃料成本:用二次函数拟合,优化中一般线性化或分段线性化处理。
- 储能运维成本:按充放电电量计费,覆盖日常维护和设备损耗。
- 储能衰减成本:按上一节描述的吞吐电量折算方法计费。
- 柔性负荷调节补偿成本:为用户削减或转移负荷支付的费用。
- 弃风弃光惩罚:可再生能源没有全额消纳时的惩罚项,数值设置要高于其他边际成本,才能保证优化优先消纳清洁能源。
这里头的关键技巧是"惩罚系数的尺子"。如果弃电惩罚取1万元/MWh,而购电成本才几百元/MWh,那优化会不惜一切代价消纳新能源,哪怕储能效率差、充放损耗高也硬着头皮上。反之如果惩罚取几百元/MWh,和购电成本相当,弃电惩罚就失去了约束力。我一般取弃电惩罚为边际购电成本的5到10倍,这样既能保证消纳优先,又不会让惩罚项在数值上淹没其他目标项。
4.2 约束体系的层次结构与线性化处理
约束条件是模型里最容易出错也最考验功力的地方。完整的约束体系大致分五层:
第一层是系统功率平衡约束。每个时段所有资源的出力之和加上联络线净购电功率,必须等于该时段负荷总和。这是硬约束,任何时刻都不允许破坏。
第二层是常规机组与分布式电源的运行约束。机组有出力上下限、爬坡上下限、最小启停时间;光伏风电的出力不能超过预测值,允许向下弃电。
第三层是储能系统约束。包括充放电功率不能超过额定功率、SOC递推关系 S(t+1) = S(t) + (η_ch · P_ch - P_dis/η_dis) · Δt/E_rated、SOC上下限约束,以及同一个时刻不能同时充电和放电的互斥约束。互斥约束通常用两个二进制变量加一个大M约束实现。
第四层是联络线约束。购售电功率不能超过线路容量,且购售电不能同时进行,同样需要用二进制状态变量描述。
第五层是备用约束。虚拟电厂作为一个整体,需要向上或者向下调节的能力不小于某个比例,比如系统最大负荷的10%,这部分能力由储能剩余可放功率、机组可上调功率、可中断负荷加总得到。
关于线性化,有一点需要特别注意。如果储能衰减成本用了关于放电功率的凸函数,直接放进线性规划模型里是没法求解的,需要进行分段线性近似。具体做法是把放电功率区间拆成若干段,每段对应一个斜率,然后用二进制变量选取当前段。Matlab里用YALMIP的pw()函数或者自己写约束都可以实现。
4.3 权衡系数怎么定:从目标优先级到敏感性分析
前面提到的所有惩罚系数和衰减系数,本质上都是对"灵活性和成本哪个更重要"的量化回答。系数定得不同,最优解完全不同,所以这一部分要当作独立的工作来做,不能拍脑袋。
我的习惯是三步走。第一步,跑一个最保守的基例:储能不参与调度、弃电惩罚取最低值,看看系统的总成本和弃电率基线是多少。第二步,逐步增大储能参与力度和惩罚系数,每改一次参数就记录一组"总成本-灵活性指标"的结果。第三步,把这些结果画成散点图,观察储能配置从无到有、从少到多时,灵活性的边际改善在哪个拐点开始衰减明显。那个拐点附近的值,就是虚拟电厂运营者最关心的经济最优运行点。
这种参数敏感性分析还有一个附加价值:它能为论文或者项目报告提供非常有说服力的图表素材。很多SCI论文里的"不同权重系数下优化结果对比图",就是这么一张一张扫描出来的。
5. Matlab实现要点与求解器陷阱:从状态向量到YALMIP约束写法
5.1 场景生成与参数表的数据组织
Matlab代码实现的第一步,不是写优化器,而是把数据组织清楚。我在实际复现时通常用结构体管理所有输入参数,这样后期做敏感性分析时只需要改结构体里某个字段,不会因为变量命名混乱而改出隐藏bug。
%% 虚拟电厂基础参数 VPP = struct(); VPP.T = 24; % 日前调度时段数,日内滚动按需调整 VPP.dt = 1; % 时间分辨率,小时 VPP.P_wt = wind_forecast; % 1x24 分散风电预测出力,MW VPP.P_pv = pv_forecast; % 1x24 分布式光伏预测出力,MW VPP.P_load = load_forecast; % 1x24 负荷预测,MW VPP.Prices = spot_price; % 1x24 分时电价,元/MWh %% 储能参数 VPP.ESS.E_rated = 200; % 额定容量,MWh VPP.ESS.P_max = 50; % 最大充/放电功率,MW VPP.ESS.SOC_min = 0.1; % SOC下限 VPP.ESS.SOC_max = 0.9; % SOC上限 VPP.ESS.eta_ch = 0.95; % 充电效率 VPP.ESS.eta_dis = 0.95; % 放电效率 VPP.ESS.deg_cost = 625; % 单位吞吐电量衰减成本,元/MWh风光预测数据怎么来?如果是复现论文,通常需要自己生成或者下载公开数据集。这里有个约定俗成的做法:先跑一个"完美预测"场景,再对预测序列叠加正态分布误差,生成乐观、基准、激进三组场景,用来测试多时间尺度滚动策略在不同预测误差下的表现。误差幅度一般取预测值的5%到20%。
5.2 决策变量、目标函数与约束的代码骨架
用YALMIP建模时,最忌讳把所有约束塞进一个超级长的列表里,出了问题根本没法查。我习惯把约束分块命名,例如Constraints_balance、Constraints_ess、Constraints_unit,拼装的时候一目了然。
%% YALMIP决策变量定义 T = VPP.T; x_soc = sdpvar(1, T+1); % 储能SOC状态变量 p_ch = sdpvar(1, T); % 储能充电功率 MW p_dis = sdpvar(1, T); % 储能放电功率 MW p_g = sdpvar(1, T); % 常规机组出力 MW p_buy = sdpvar(1, T); % 联络线购电 MW p_sell = sdpvar(1, T); % 联络线售电 MW u_ch = binvar(1, T); % 充电状态标志 u_dis = binvar(1, T); % 放电状态标志 u_g = binvar(1, T); % 机组启停状态标志 %% 约束分块构建 Constraints_balance = []; for t = 1:T Constraints_balance = [Constraints_balance, p_wt(t) + p_pv(t) + p_g(t) + p_dis(t) + p_buy(t) ... == VPP.P_load(t) + p_ch(t) + p_sell(t)]; end Constraints_ess = []; Constraints_ess = [Constraints_ess, x_soc(1) == 0.5]; % 初始SOC Constraints_ess = [Constraints_ess, x_soc(T+1) == 0.5]; % 调度周期结束回到初始SOC for t = 1:T % SOC递推 Constraints_ess = [Constraints_ess, x_soc(t+1) == x_soc(t) + (VPP.ESS.eta_ch*p_ch(t) - p_dis(t)/VPP.ESS.eta_dis)*VPP.dt/VPP.ESS.E_rated]; % 功率上下限与互斥约束 Constraints_ess = [Constraints_ess, 0 <= p_ch(t) <= VPP.ESS.P_max*u_ch(t)]; Constraints_ess = [Constraints_ess, 0 <= p_dis(t) <= VPP.ESS.P_max*u_dis(t)]; Constraints_ess = [Constraints_ess, u_ch(t) + u_dis(t) <= 1]; Constraints_ess = [Constraints_ess, VPP.ESS.SOC_min <= x_soc(t+1) <= VPP.ESS.SOC_max]; end %% 目标函数 Objective = sum(p_buy .* VPP.Prices) - sum(p_sell .* VPP.Prices) ... + sum(VPP.ESS.deg_cost * (p_ch + p_dis)) ... + sum(fuel_cost_coeff * p_g) ... + sum(curtail_penalty * (VPP.P_wt + VPP.P_pv - p_wt - p_pv));这段代码里有一个非常容易被忽略的细节:储能衰减成本同时计入了p_ch和p_dis。很多人写成本项时只算了放电侧,认为只有放电才产生收益和损耗。但实际上电池充放电都算循环寿命,充电过程一样在消耗容量老化指标,所以必须把两个方向都计入。
5.3 求解器选择与大M法的数值细节
YALMIP是一个建模工具,不是求解器。很多新手装了Matlab和YALMIP,然后发现代码跑不通,就以为是模型写错了——其实往往是缺了一个真正的混合整数优化求解器。上面的模型是MILP(混合整数线性规划)问题,需要Gurobi、CPLEX或者原生的intlinprog来求解。学术界常用Gurobi,因为它的数值稳定性和求解速度都明显优于同类工具,而且学术版申请流程已经非常标准化了。
另一个绕不开的技术细节是Big-M法的M取值。充电互斥约束里 u_ch(t) + u_dis(t) <= 1 不需要M,但"功率上限乘以状态变量"这个写法本身就已经利用了类似Big-M的思想。如果M取得太小,比如写成 p_ch(t) <= 100 * u_ch(t),但实际充电功率上限是150 MW,那可行域就被压缩错了;如果M取得太大,比如1e6,求解器在数值计算中会引入很大的舍入误差,可能出现"看起来可达"但实际上不可行的解。比较稳妥的做法是M取该量物理上限的1.2到2倍,不要用无限大的概念值。
我还建议在求解前显式设置求解器的参数,比如Gurobi的MIPGap容忍阈值,默认0.01%在某些大模型上会导致求解时间过长,我通常设置为0.1%到1%之间,能在几乎不损失解质量的情况下大幅缩短仿真时间。
6. 复现踩坑记录与参数敏感性:同样的模型为什么有人收敛有人爆炸
6.1 不可行解的排查链路:先约束后目标
我复现这类模型时踩过的第一个大坑,就是碰到"infeasible problem"结果,然后对着代码发呆半小时。后来总结了一套排查链路,现在分享给所有做类似工作的读者。
第一步,锁定初值约束。SOC初始值设成0.5,同时要求周期末回到0.5,这个条件本身没问题。但如果中间某个时段负荷特别高、可再生能源很低、储能功率又设了上限,系统可能根本没有办法既满足功率平衡又让SOC回到目标值。这时候先去掉SOC末值约束,如果模型立刻可行,说明问题出在"总能量收支不匹配",需要放宽末期SOC范围或者允许一定的SOC松弛惩罚。
第二步,检查功率平衡约束的符号。这是新手最容易犯的错误——充放电功率在等式两边的符号写反了,模型要求"发电机加上储能放电等于负荷加储能充电",方向一颠倒,问题直接进入不可行区。我排查这个问题的办法是在调试模式下把每个时段的功率平衡残差单独打印出来,人工核对前三个时段就能看出符号问题。
第三步,检查备用约束。备用容量要求是虚拟电厂作为整体参与市场的必要条件,但它经常成为约束过紧的元凶。比如要求在负荷低谷时段也保证向上备用容量不低于某一绝对数值,那时候大部分机组都在低出力区间,向上空间本来就有限。建议把备用约束改成动态比例,例如取当前净负荷的某个百分比,而不是一个固定的大数。
6.2 M参数、惩罚系数与滚动窗口更新的实际取值经验
数值病态是另一个高频坑。假设机组最大出力是200 MW,有人图省事把Big-M的值写成1e5,结果求解器精度异常,约束被莫名其妙地违反或者收敛到非最优解。我现在的取法遵循一个原则:M的取值永远和物理量的实际范围挂钩,最多放大一倍留出余量。用禁运表达来举例可能不恰当,但M=400对于200 MW的机组出力约束就是够用的,没必要再大。
惩罚系数的取值同样要有物理意义。弃电惩罚的物理意义是"少发一单位清洁能源造成的社会损失或考核损失",这个值应该在灵敏度分析里给出上下界,而不是拍脑袋。通常我做两次扫描:第一次固定其他参数,把弃电惩罚从100扫到2000元/MWh,看弃电率变化;第二次固定弃电惩罚,把储能衰减成本从200扫到1000元/MWh,看储能利用率变化。两次扫描画出的灵敏度曲线,几乎可以直接作为论文里的收敛性和鲁棒性分析素材。
再讲一个滚动更新实现上的细节。日内滚动优化要求每个滚动周期开始时更新四类数据:当前实测SOC、最新的超短期风光预测、已经执行的实际出力、联络线实际功率。有些人写代码时只更新了预测数据,却忘了把上一时段实际SOC读回来作为初值,导致模型里SOC是理论值,实际执行时偏差越滚越大。正确的做法是在滚动循环体里显式赋值:x_soc_initial = measured_soc(now),不要引用日前模型的计划值。
6.3 从复现走向扩展:把固定模型变成可调工具箱
做完基础复现之后,模型不应该是一潭死水。我通常会做三件事让它变成可以长期复用的"工具箱"。
第一是把所有参数抽出来做配置文件,包括储能成本曲线、负荷特性、电价序列、预测误差设置,都放在一个结构体里,换一个项目场景就换一份配置,而不是改代码。第二是保留敏感性分析脚本,它不仅能帮自己理解模型行为,也能在答辩和项目评审时拿出量化数据支持结论。第三是给模型加"开关"——衰减成本开关、弃电惩罚开关、备用约束开关,用逻辑变量控制哪些约束计入、哪些不计入,这样你能在几分钟内对比不同模型设置对结果的影响,对理解文献中各种变体模型非常有用。
我做项目时的体会是,最高价值的不在于某个具体的结果曲线有多漂亮,而在于建模和调参过程中积累的这些判断力:知道哪个约束在什么场景下会过紧、知道哪类参数在同等成本约束下对灵活性影响最大。能把这一套捋顺,再换到别的园区、别的省份、别的电池参数,手上有了方法论,心里就不慌。