1. 为什么是"两阶段"而不是"一杆子到底":滚动调度的底层逻辑
先说个很现实的问题:综合能源系统调度这件事,如果只靠一次优化算完24小时,结果往往没法直接用。原因很简单,你算的时候用的负荷预测、光伏预测、风电预测,和实际运行时的真实数据之间总有差距,而且这个差距会随着时间推移越来越大。用一句通俗的话说:你拿着昨天的天气预报决定今天穿什么,十有八九会被淋个措手不及。
我做这个项目最开始的动机,就是不想让调度策略停留在"算完就完"的理论层面。综合能源系统里,电、气、热多能耦合,储能、燃气轮机、锅炉、吸收式制冷机这些设备都有各自的运行约束,再加上用户侧负荷还有很大的调节空间,如果不用一个合理的时序框架把这些东西串起来,整个系统的经济性和可靠性很难同时兼顾。这里引入分时电价需求响应,本质上是想用价格信号撬动用户侧的调节潜力——把高峰时段的负荷往低谷挪,用便宜的电替代贵的天然气,让整个系统在经济性和低碳性之间找到一个平衡点。
很多刚接触这个方向的同学都会问:为什么要分日前和日内两阶段?一个模型把全天都管了不行吗?答案是:不行。原因我在前面已经说了一半——预测误差。剩下的一半在于,电力的物理特性决定了供需必须实时平衡,但设备响应是有时间尺度的。燃气轮机启停、储能充放电切换、吸收式制冷机启动,这些动作的响应速度和成本完全不是一个量级。如果所有决策都放在同一个时间尺度上做,要么因为颗粒度太粗导致日内频繁调整,要么因为决策太频繁导致求解压力过大、结果在实际中根本无法执行。
打个比方,就像你规划一次长途自驾:出发前你会做一个整体路线规划,决定什么时候加油、什么时候休息、走哪条高速——这是日前调度;但真正上路后,你会发现遇到堵车、天气变化,必须随时微调路线和休息点——这是日内滚动优化。两阶段的核心思想,就是用日前做"粗调",搭好大框架;用日内做"细调",根据最新预测数据实时修正偏差。
具体到本项目,日前调度以1小时为时间间隔,对全天24个小时做整体优化,得到各机组的启停计划、储能充放电策略、与电网的交换功率计划,以及需求响应资源的预调度方案。日内滚动优化则以15分钟为间隔,滚动优化周期4个小时,每15分钟更新一次预测信息,在前4个小时的窗口内做精细的功率分配和调整。这样就形成了一个递进修正的决策链条:日前定基调,日内做修正,两个时间尺度互相配合,兼顾了全局最优性和局部响应速度。
这个框架的实用价值在哪里?我在实际测试中发现一个很典型的现象:如果只做日前调度,当光伏实际出力比预测低30%时,系统会依赖电网买电来补缺,恰好又赶上峰时段电价,单日购电成本直接飙上去。而加入了日内滚动优化之后,系统会在发现光伏出力下降的第一个15分钟窗口内,就提前调整储能出力、削减可转移负荷,把高峰时段的购电需求提前消化掉。这就是为什么行业里愿意用两阶段而非单阶段模型——它不是在理论上更漂亮,而是在真实场景里真的更省钱、更可靠。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数学模型才是重头戏:目标函数与约束条件怎么设计才算"物理可行"
很多刚接触这个方向的人会以为,优化调度就是装个工具箱、导入数据、点一下运行。真上手做了才发现,80%的功夫花在数学模型的构建上。模型建得不对,求解器再强也给你算出一个物理上根本无法执行的"最优解"。
2.1 目标函数的三层结构
本项目的目标函数可以拆成三层来看:
- 第一层:购能成本。 系统需要从上级电网购电,还可能需要购买天然气。这部分是运行成本的大头,尤其是在分时电价机制下,峰时段电价比谷时段高出很多,购电策略直接决定经济性。
- 第二层:设备运行维护成本。 燃气轮机、燃气锅炉、吸收式制冷机、电锅炉、储能电池,都有对应的单位运维修费用。虽然单台设备运维成本不高,但系统里设备一多,24小时累加起来就是一笔不小的开支。
- 第三层:需求响应补偿成本。 用户响应了削峰指令、改变了用电习惯,需要给予一定的补偿激励。这部分的单位成本通常低于峰时段电价差,所以在经济上是有利可图的。
这三层加起来,构成日前和日内两个阶段各自的目标函数。需要注意的是,两个阶段的目标函数并不完全一致。日前阶段侧重全局经济性,日内阶段更加注重跟踪日前计划的同时、尽量压低实时调整带来的额外费用。
2.2 设备层约束的数学表达
综合能源系统里的设备约束,说白了就是要回答几个问题:每个设备出力范围是多少?爬坡速度有多快?储能充放电有没有容量限制?下面我挑几个典型的设备说清楚。
燃气轮机(CHP机组):
燃气轮机的发电功率存在上下限约束,同时它的热电比也不是固定不变的,通常在一定范围内可调。在数学上可以表示为:
code复制P_CHP_min ≤ P_CHP(t) ≤ P_CHP_max
H_CHP(t) = η_CHP_h * F_CHP(t) (热出力与燃料消耗量的关系)
Q_CHP_min ≤ Q_CHP(t) ≤ Q_CHP_max (产热侧约束)
这里有个细节容易踩坑:燃气轮机的电功率和热功率是耦合的,不能单独约束。单独约束电功率和热功率的上下限还不够,必须把二者之间的可行域关系写进去,否则求解结果可能让机组的发热和发电比例严重偏离物理特性。
储能系统:
储能约束的核心是三件事:充电功率上限、放电功率上限、SOC(荷电状态)动态变化。其中SOC的变化是跨时段的,用如下递推式表示:
code复制SOC(t+1) = SOC(t) + (η_c * P_c(t) - P_d(t) / η_d) * Δt / E_capacity
SOC_min ≤ SOC(t) ≤ SOC_max
充放电状态不能同时为1,这个逻辑约束在YALMIP里通常用二进制变量来处理,求解的时候会引入0-1变量,问题就变成了MILP。
电锅炉与燃气锅炉:
这两类设备属于纯转换设备,约束比较简单:出力在额定上下限之间,爬坡速率有边界。但要注意电锅炉的耗电会改变系统电负荷平衡,燃气锅炉的耗气会改变天然气购买量,所以要放进各自的能量平衡方程里看。
2.3 需求响应约束怎么建模
分时电价需求响应是这个项目的灵魂,约束怎么建模直接决定策略是否有效。基于我自己的实践,需求响应建模有两种常用方式,本项目用的是激励型需求响应,即通过价格信号或补偿合同引导用户调整负荷。
具体来说,负荷可以分成三类:
- 固定负荷:无法参与响应,必须满足。
- 可削减负荷:用户可以在高峰时段减少使用量,但削减量有上限(比如不超过该时段总负荷的15%),同时需要付出补偿成本。
- 可转移负荷:这部分负荷可以从高峰时段转移到低谷时段,但转移前后要保持总量守恒(这里遵循能量守恒定律)。
code复制P_load(t) = P_fixed(t) + P_shed(t) + P_shift(t)
0 ≤ P_shed(t) ≤ α_shed * P_load_max(t)
Σ P_shift_in(t) = Σ P_shift_out(t)
这个建模方式看起来不算复杂,但在实际代码实现中,可转移负荷的时序耦合逻辑特别容易写错。我最初写的时候就没有考虑"转移守恒",结果优化出一组完全不可行的解——低谷时段凭空多出了大量负荷,而高峰时段没有相应削减,整个系统的供需平衡被打破,求解器报错不说,就算强行给出结果,也是没有物理意义的。
注意:需要特别谨慎对待可转移负荷的守恒约束。它要求转移出去的和转移回来的总量必须保持一致。如果一个家庭早上8点不用洗衣机、改到23点用,那么总的用电量在一天内是不变的,变的只是分布的时段。这个逻辑必须在约束里体现清楚。
2.4 日前-日内衔接的边界条件
两阶段模型的衔接,是另一个藏坑的地方。日内滚动优化不是从零开始重新算一遍,它必须接收日前阶段的某些决策结果作为边界硬约束。比如:
- 日前确定的机组启停状态(启停属于慢变量,日内一般不动,否则机械磨损和启停成本不划算)
- 日前计划的储能SOC末端值(日内优化时储能起始SOC由实际运行状态给出,末端值仍参考日前计划的预留值)
- 与电网交换功率的爬坡边界(日内调整不能偏离日前计划太多,否则对电网不友好)
这个衔接如果你在代码里写漏了,日内的滚动优化就会"放飞自我",每15分钟重算一次,结果完全偏离日前的整体规划,系统的运行成本和设备磨损都会失控。我在第一版代码里就踩过这个坑,后面测试时发现日内储能的充放电策略跟日前计划完全对不上,储能一天之内反复深度充放,损耗极大。
3. YALMIP建模与求解器选型:从MIQP到MILP的坑位在哪里
模型建好了,接下来说说求解这件事。本项目采用Matlab环境下的YALMIP工具箱进行建模,求解器用的是IBM CPLEX。之所以选这个组合,一方面是YALMIP的语法接近数学表达式,建模效率高、代码可读性好;另一方面CPLEX对混合整数线性规划的求解能力非常成熟,大规模问题也能在可接受时间内收敛。
3.1 为什么不是直接调fmincon或使用智能算法?
不少初学者会首选fmincon或者遗传算法、粒子群算法这类智能优化算法来求解调度问题。我的经验是:如果只是做一个简单的小系统demo,fmincon确实够用;但一旦模型引入二进制变量(比如机组启停、储能充放电状态),问题性质就变成了混合整数规划,fmincon并不能直接处理这类问题,强行用罚函数法处理0-1约束,效率和精度都很差。
至于遗传算法和粒子群,它们在处理非线性、非凸问题时有一定优势,但有两个硬伤:一是无法保证全局最优解,每次运行结果可能不一样;二是计算速度慢,日内滚动优化每15分钟就要重新求解一次,动辄数分钟才能收敛的算法在实际调度中根本跑不起来。
CPLEX配合YALMIP,是把混合整数问题交给专业求解器来处理,用精确算法求解,速度和最优性都有保证。 这一点在工程落地时非常重要。
3.2 MILP线性化处理
在建模过程中,会遇到一个经典问题:目标函数里有些项是非线性的,比如储能充放电的单位成本,如果充电和放电的单位成本不同,就会出现阶跃函数;燃气轮机的发电效率如果是出力的函数,也会引入非线性项。
处理思路很简单:能避免的非线性就避免,不能避免的就线性化。以储能为例,充电和放电的损耗分解为:
code复制P_c(t) * η_c 和 P_d(t) / η_d → 引入二进制变量 u(t),让 P_c(t) ≤ M*u(t),P_d(t) ≤ M*(1-u(t))
这里的M是大M法的辅助参数,在数值上取值要足够大,但也不能太大,否则会让求解器数值不稳定。我看到很多代码里M值随便设一个1e6,结果CPLEX求解时发出数值警告,有的甚至直接给不出可行解。一般根据实际变量量纲,取值在100到1000之间就够了。
YALMIP中二进制变量声明也很简单:
matlab复制u = binvar(T, 1); % T为时段数
P_c = sdpvar(T, 1);
P_d = sdpvar(T, 1);
Constraints = [Constraints, P_c <= M*u, P_d <= M*(1-u)];
3.3 求解器参数设置
CPLEX在YALMIP里调用时,有几个参数我建议一定要关注:
cplex.settings.mip.tolerances.mipgap:MIP相对最优间隙,设置为0.01或更小,保证解的质量。cplex.settings.mip.tolerances.integrality:整数变量允许误差,默认1e-5即可。cplex.settings.parallel.mode:设成1,可以让多核加速求解。
实际项目中,日前阶段由于要算的变量多、约束多,求解时间可能在几十秒到几分钟;日内阶段时间窗口缩小到4小时、步长15分钟,变量少了很多,通常几秒就解完了。如果某一轮滚动求解时间超过5秒,基本意味着约束之间出现了冲突,需要返回检查约束条件而不是单纯等待。
4. 滚动优化的核心:状态变量传递与参数更新逻辑
滚动优化本质上沿着时间轴不断"滚动"地往前推,每一步只优化未来一个有限时域,然后只执行第一个时段的决策,到下一个时刻再基于最新的状态和预测重新优化。这套思路在学术上叫模型预测控制(MPC),在电力系统调度里落地,最大的难点在于状态变量的传递和参数的实时更新。
4.1 负荷预测与可再生能源出力的滚动修正
我实际测试中发现,负荷预测误差整体较小,但光伏出力的预测误差起决定性影响。光伏出力受云层、温度影响较大,尤其在多云天气,15分钟内的出力可能从满发跌到20%再反弹回来。日内滚动时,每15分钟重新读取一次最新的光伏出力预测数据,代入优化模型重新求解,这样就能把预测误差对运行决策的影响控制在小范围内。
具体到实现上,模拟预测数据我采用的是"实际出力叠加高斯扰动"来模拟预测误差:
matlab复制% 模拟光伏预测误差,标准差设为实际出力的10%
pv_forecast = pv_actual + normrnd(0, 0.1 * pv_actual, size(pv_actual));
pv_forecast = max(pv_forecast, 0);
这个方法虽然简单,但已经可以有效地测试日内滚动对预测误差的鲁棒性了。实际项目中,预测数据可能来自气象预报系统或短期负荷预测算法,但仿真测试时用这种加扰的方式,能完整地验证策略的调节能力。
4.2 储能SOC跨时间尺度的衔接
储能SOC是两个时间尺度衔接中最关键的状态量。日前调度结束时,对应每个时刻都有一个SOC计划值。日内滚动优化开始时,当前SOC要取实际运行值,而不是日前计划值——原因很简单,日前计划是"计划",实际SOC会因日内调整而偏离计划。
但如果完全抛弃日前计划的SOC轨迹,日内优化又容易短视化:每个滚动窗口只顾着当下4小时的经济最优,把储能电量放到很低的水平,毁掉了后续时段的调节空间。因此,我在实现中加入了SOC末端惩罚项:
matlab复制% 日内滚动:目标函数中加入SOC末端偏离惩罚
Objective_inner = Objective_inner + penalty_factor * (SOC_end - SOC_ref_end)^2;
这个惩罚因子的取值,直接决定了日内滚动对日前计划的"粘性"。罚得太轻,日内策略短视;罚得太重,日内滚动就退化成追着日前计划走,失去了对预测误差的修正能力。我调了几轮参数,最终选定惩罚系数为购电电价的0.5倍,在跟踪性和灵活性之间取得了较好的折中。
4.3 各阶段决策变量与参数传递
两阶段代码实现过程中,下面这些数据必须明确传递:
| 传递内容 | 传递方向 | 说明 |
|---|---|---|
| 机组启停状态 | 日前 → 日内 | 日内阶段固定启停,不再优化 |
| 燃气轮机基础出力区间 | 日前 → 日内 | 日内只调整出力增量 |
| 储能SOC初值 | 实际状态 → 日内 | 每个滚动窗口开始时读入 |
| 与电网交换功率计划值 | 日前 → 日内 | 日内设置偏离惩罚 |
| 需求响应预调度结果 | 日前 → 日内 | 日内微调,但约束在总量守恒内 |
我把这些传递关系封装成了一个函数,每次滚动求解前先调用它,确保两个阶段之间的数据一致性。这在代码组织上是一个小而有效的设计:避免因变量命名混乱导致赋值错误,排查起来也方便。
5. 从代码到手把手复现:工程化实现中的关键细节
5.1 五分钟上手的数据驱动型代码架构
工程化实现要想稳住,我推荐把代码按模块划分,每个模块一个函数,三个核心函数如下:
init_system_parameters.m——定义系统所有设备参数,包括容量、效率、爬坡速率、分时电价、初始负荷等。build_day_ahead_model.m——构建日前调度的决策变量、约束和目标函数,调用CPLEX求解。build_intraday_rolling_model.m——构建日内滚动优化模型,包含滚动窗口、SOC传递、预测更新逻辑。
主函数的整体流程用一个伪代码可以概括如下:
text复制初始化系统参数
读取历史/预测数据(负荷、光伏、风电、电价)
求解日前优化,获得机组启停计划、储能SOC参考轨迹、电网交换功率参考值
设置仿真总时长T_total = 24h,滚动步长Δt = 15min
for t = 1 : Δt : T_total
更新当前时刻实际运行状态(模拟带扰动)
更新未来4h负荷/光伏/风电预测数据
调用日内滚动优化,求解未来4h内的最优策略
执行第一个时段的决策(功率指令下发)
更新储能SOC、累积运行成本
end
输出结果:绘制日前/日内调度曲线,对比两种策略的成本
这个结构下,代码的每一步都有明确的输入、输出,不会出现变量在模块间乱飞的情况。
5.2 一个值得仿效的目标函数实现技巧
YALMIP建模过程中有一个小习惯很实用——不要把目标函数一行写完。一行长目标函数既难以检查,也容易写错。我习惯把成本分成若干子项,逐行叠加:
matlab复制Objective = Objective + sum(Price_buy .* P_grid_buy) ... % 购电成本
- sum(Price_sell .* P_grid_sell) ... % 售电收益
+ sum(C_gas * F_gas) ... % 购气成本
+ sum(OM_CHP .* P_CHP) ... % CHP运维成本
+ sum(OM_ESS .* (P_c + P_d)) ... % 储能运维成本
+ sum(Price_DR .* P_shed); % 需求响应补偿
这样写的好处有三个:一是每条成本项的物理意义清晰;二是后续调试时可以直接注释掉某一行,看某一项成本对优化结果的影响;三是在对比不同场景(比如无需求响应、无储能)时,只需要删减对应项即可。
5.3 数据可视化:怎么用一张图呈现调度效果
研究做完,如果不能用清晰的图把面向不同时段的调度结果展示出来,效果会大打折扣。我一般会画这样几张图:
- 电功率平衡图:堆叠图展示电负荷如何由光伏、风电、CHP、储能放电和电网购电共同满足,峰谷规律一目了然。
- 储能SOC曲线图:对比日前计划SOC与日内实际SOC的差异,能直观看出滚动优化的修正效果。
- 分时电价与购电策略对比图:把电价曲线和购电功率曲线放在同一张图,观察峰时段购电是否明显减少、谷时段购电是否适当增加。
- 需求响应负荷调整图:展示可削减负荷和可转移负荷在峰谷时段的分布变化。
用Matlab的plot和stairs两个函数结合就能画出来。我画图时会注意把日前和日内的结果用虚线/实线区分,图例标注清楚,分辨率保存成300dpi,这样直接放在论文里没有问题。
6. 实测中的那些坑:从SOC漂移到电价断崖的翻车现场
该说一说实践过程中踩过的坑了。这部分的价值,有时候比模型本身更值得被记录。
6.1 踩坑一:储能SOC在日内的"漂移"
我在第一版日内滚动代码中,没有把日前SOC计划作为末端参考约束加入目标函数,结果连续滚动几个小时后,储能SOC被日内优化策略"掏空"——日内优化只关注自己4小时窗口的经济性,把储能电量压到接近下限。等到傍晚负荷高峰真正到来时,储能已经没有容量可放了,系统只能高价购电,整体经济性反而不如单纯日前调度。
修复方法就是我前面说的,在日内目标函数中加入SOC末端偏离惩罚项,让日内决策不至于过分"短视"。经过这轮修正后,整天的总运行成本下降了约7.3%。
6.2 踩坑二:分时电价峰谷切换时段的"断崖"行为
分时电价有一个特点:在峰转谷或谷转峰的时间节点,电价出现一个台阶式突变。优化算法会利用这个突变做一个"极限操作"——在谷时段的最后一刻疯狂充电,在峰时段的第一刻就停止购电。如果储能功率和充放电约束允许,这个操作在数学上是合理的,但在实际运行中,会给电网和设备带来冲击,而且充放电瞬间的功率变化也超过了设备实际的爬坡能力。
对策有两个:一个是在储能约束中加入充放电爬坡约束;另一个是在电价切换时段加入松弛变量并加以惩罚。
6.3 踩坑三:需求响应参数设0后,优化结果直接翻车
出于测试对比的需要,我曾经把需求响应的最大削减比例设成0,意味着负荷不可削减。结果发现,系统在高峰时段电功率平衡约束被破坏,CPLEX直接报inf或infeasible。道理也很简单:去掉需求响应之后,系统的可调资源减少了,如果储能容量和机组出力上限刚好不能满足峰值负荷,模型就没有可行解。
这个现象提示一个建模上的经验:不要把设备的可用边界卡得太死,给约束留一定的松弛空间,否则一旦现场数据与预测数据出现偏差,优化模型就会无解。 在学术研究中无解还可以接受,但在工程落地时这是完全无法接受的。
6.4 求解器数值警告的排查经验
YALMIP调用CPLEX时偶尔会冒出数值警告,提示矩阵中某些系数过大或过小。排查思路一般是先检查大M值是否过大(比如1e6),再检查变量的量纲是否统一——是kW还是MW?是元还是万元?量纲不一致会让约束系数出现几个数量级的差异,导致数值不稳定。把所有参数统一成同一量纲,数值问题基本能消除一大半。
另外,YALMIP中建议显式地对每个变量设置上下界而非全部依赖约束,这样求解器预处理的效率会更高。
7. 代码实现效果:哪些结果值得重点关注
代码跑通后,下面几个结果是我强烈建议你重点关注的,因为它们直接反映模型质量。
7.1 经济性对比:需求响应到底省了多少钱
我测试的算例中,基础参数如下:典型日负荷峰值1200kW,光伏装机500kW,风电装机200kW,CHP额定电功率300kW、热功率350kW,储能容量600kWh、最大充放电功率150kW,分时电价峰平谷三段价格分别为1.2、0.8、0.4元/kWh。
三种场景的日前调度结果对比如下:
| 场景 | 日运行成本(元) | 峰时段购电占比 | 需求响应补偿成本(元) |
|---|---|---|---|
| 无需求响应、无滚动修正 | 15680 | 42% | 0 |
| 有需求响应、无日内滚动 | 14920 | 33% | 620 |
| 有需求响应、有日内滚动 | 14130 | 28% | 590 |
从这个结果可以看到,需求响应带来的成本降幅约4.8%,日内滚动优化在此基础上再降约5.3%,两个机制叠加的综合降本效果约为9.9%。峰时段购电占比从42%降到28%,削峰填谷的效果非常明显。
7.2 调度曲线的合理性判断
拿到调度结果后,我习惯先检查储能SOC曲线是否平滑:SOC应该呈现"谷充峰放"的循环特征,不应出现频繁的震荡。如果SOC曲线上下剧烈跳动,要么是充放电约束没写对,要么是目标函数里缺少对充放电切换的惩罚。
再看电功率平衡:每个时段内,电源出力加储能放电加购电,应该严格等于电负荷加储能充电加卖电(如果有余电上网)。如果平衡方程残差不归零,说明约束写错了,必须回头检查。
7.3 鲁棒性测试:预测误差加大后系统还能不能扛住
工程上做完了基本场景,我还做了极限测试:把光伏预测误差从10%逐步增大到30%,观察系统运行成本的变化幅度。结果很符合预期:有日内滚动修正的系统,成本增幅明显小于纯日前调度的系统。这说明滚动优化的作用不只是"锦上添花",而是在源荷不确定性加剧时,发挥关键保底作用。
这个结论也印证了本项目的价值:两阶段日前-日内滚动优化调度,不是一个"炫技"的模型,而是综合能源系统在真实运行环境下,兼具经济性和可靠性的工程化解决方案。
8. 代码调试的实用建议与验证思路
最后整理几个代码调试和结果验证的实用建议,都是我自己从零开始调通这个项目后总结的经验。
8.1 从简单场景开始"试错"
调试优化模型时,不要一上来就使用完整数据、完整模型。先用一个简化算例:把时间步长改为1小时、系统只保留储能和电网交互、把CHP和需求响应暂时禁用。跑通之后再逐步加回设备、加回约束,每加一项就验证一次结果。这个方法能帮你在最短时间内定位是哪个设备、哪条约束导致模型不可行。
具体来说,我推荐这样的调试顺序:
- 只有电网购电+电负荷,验证最简单的功率平衡和成本计算。
- 加入储能和SOC约束,验证充放电时序的合理性。
- 加入CHP和燃气锅炉,验证热电联产和热负荷平衡。
- 加入需求响应,验证可削减和可转移负荷的守恒约束。
- 最后再启动日内滚动模块,验证状态传递和参数更新的正确性。
8.2 记录日志,别靠肉眼逐个时段检查
当仿真时长拉长到24小时、滚动次数上百轮时,靠肉眼逐时段检查结果是不现实的。建议在代码中添加日志功能,把每轮滚动优化中遇到的不可行时段、调整量超限时段、SOC越限时段都记录下来,然后用日志筛选定位问题。
我用的一个简单做法是:在滚动循环里加一个if判断,一旦求解器返回problem值不为0,就记录当前时刻和相关的输入输出数据,保存到workspace变量中。调试时直接查看这些变量,定位效率提升不少。
8.3 结果对比矩阵:确保每个模块都在发挥作用
验证模型的有效性,可以用一个"组件启停记录表"来做对比。比如:
- 只开日前调度:成本基准值是多少?
- 日前+日内滚动:成本下降了百分之几?
- 日前+需求响应:成本下降了百分之几?
- 日前+日内+需求响应:成本是否最低?
如果组合起来的结果低于任意单独的组件,说明各模块之间是互补的,模型设计是合理的;如果出现组合后成本反而上升的情况,多半是约束之间的耦合关系写错了,或者目标函数中各项成本的权重不匹配。
8.4 关于YALMIP调试的一个小技巧
YALMIP的optimize函数返回的info结构体里,包含了很多有用的诊断信息。其中info.yalmiptime和info.solvertime分别表示建模时间和求解时间。如果yalmiptime异常高,说明约束条件里面可能存在冗余或冲突,模型需要简化;如果solvertime异常高,说明模型复杂度超出了求解器的承受范围,需要考虑约束收紧或变量削减。
另外建议运行前用yalmiptest检查一下YALMIP和求解器的安装配置是否正确,这能避免很多莫名其妙的低层报错。
