做电动汽车有序充电调度研究,最让我头疼的一段时间,是单层优化怎么调都“调不出人味”。什么意思?你站在电网公司或者充电聚合商的角度,用一个目标函数把削峰填谷和用户充电费用加权求和,解出来的方案在数学上很漂亮——用户整体电费最低、负荷曲线最平,但落到真实场景里根本跑不通:用户凭什么按照你排的时段充电?人家只看电价,电价贵就不充,电价便宜就扎堆充。加权目标是把两个利益方强行捏在一起,而双层优化是老老实实承认“上层有上层的目标、下层有下层的小算盘”,再把两边的行为用一个主从博弈结构串起来。这篇文章我就以“基于双层优化的电动汽车优化调度MATLAB代码研究”为主题,把从问题建模、求解算法到MATLAB代码实现、调参踩坑的完整过程过一遍,给正在做电动汽车入网、有序充电、V2G方向的同学和工程师一份可以直接照着复现的经验。
1. 为什么是“双层”:单层优化解决不了的利益冲突
1.1 一次失败的加权优化实验
先说我早期踩过的一个坑。当时我做一个住宅区充电桩群的调度项目,目标很简单:削峰填谷,同时别让用户的充电费太离谱。于是我建了一个单层模型,目标函数写成电网负荷方差的加权项加上用户总充电费用的加权项,权重自己拍脑袋定,调了几轮觉得效果不错,曲线也挺平。结果一细看就露馅了:模型给出的最优方案里,大量电动车被安排在凌晨三点充电,因为那个时段基础负荷最低,对削峰填谷贡献最大。
问题是,真实用户凌晨三点大概率在睡觉,而且不少车主第二天一早就要用车,头天晚上插上枪希望尽快充满。我那个单层模型完全没考虑用户的到达时间、离开时间、期望电量这些个体约束,等于在替用户做根本不可能执行的决策。更关键的是,如果电价是统一的,用户获得自由度之后,所有人都会选择一回家就充电,原来平的负荷曲线立刻被拍出一个晚高峰。这说明一个问题:只要用户是独立决策者,调度方案就必须考虑“用户会怎么反应”,而不是替用户直接拍板。
1.2 电网侧与用户侧的天然博弈结构
后来我换成了双层优化,思路一下就清楚了。上层是电网或充电聚合商,负责制定策略——可以是给每个时段定电价,也可以直接给每辆车分配充电功率;下层是每辆电动车或者每个充电站,收到上层的信号之后,以自身充电费用最小(或者综合满意度最高)为目标,决定实际的充电功率曲线。
这个结构在博弈论里叫Stackelberg主从博弈:上层是leader,下层是follower,leader在做决策的时候,必须预判followers会怎么响应。数学上写成:
- 上层问题:
min F(x, y*(x)),其中y*(x)是下层的均衡响应 - 下层问题:
y*(x) = argmin_y f(x, y),给定上层决策x,下层优化自己的目标
这个嵌套结构天然反映了利益冲突:电网想削峰填谷,用户想省钱,两边不可能同时达到完美,最后的结果一定是一个折中。而这个折中不是靠拍权重拍出来的,是博弈出来的。我后来把同样的问题用双层重新建模,出来的方案终于“像人干的事”了——用户在电价低且自己方便的时候充电,电网也在高峰期约束了总功率,两边都做出了合理让步。
1.3 双层优化适用的场景边界
不过我要泼一盆冷水:双层优化不是万能的,也不该无脑用。如果你的场景里所有充电桩都是同一家公司自营自管,电动车也都是公司的可控资产,那本质上是一个单层资源分配问题,强行套双层只会给自己找麻烦。双层优化的适用场景,我总结下来有三个特征:
- 存在多个独立决策主体,各自目标不一致(电网 vs 车主,或者配电网运营商 vs 充电站聚合商)
- 上下层之间有明确的耦合变量,最常见的就是价格信号或者调度指令
- 下层主体的响应会反作用于上层目标,也就是存在反馈回路
如果你手头的项目符合这三条,双层建模就是合理的;如果只是单纯想知道“所有车怎么充最平”,单层就够了。这个判断在论文开题和工程方案评审时能帮你省掉很多解释成本。
2. 数学模型构建:上层目标、下层目标与耦合机制
2.1 上层模型:聚合商或电网的调度目标
先聊上层。上层决策者的身份不同,目标函数差别很大,最常见的有三种写法。
第一种是最经典的削峰填谷,目标函数是负荷方差最小化:
min Σ_t ( P_base(t) + Σ_i P_ev(i,t) − P_avg )²
其中P_base(t)是t时段的基础负荷,P_ev(i,t)是第i辆车在t时段的充电功率,P_avg是调度后全天总负荷的平均值。这个目标做出来的曲线最平,视觉效果最好,也是大部分论文里主图用的。
第二种是最大峰值最小化,也就是最小化全天最高负荷:
min z,约束加一个z ≥ P_base(t) + Σ_i P_ev(i,t)对每个时段成立。这个目标对配电网更友好,因为变压器容量就是按峰值设计的,削掉峰值等于省了扩容投资。
第三种是聚合商利润最大化:聚合商从批发市场买电,再以零售价卖给车主,目标写成
max Σ_t ( λ(t) − c_buy(t) ) · Σ_i P_ev(i,t)
其中λ(t)是零售电价,c_buy(t)是购电成本。利润最大化天然会把充电负荷往电价差大的时段引导,如果配合峰谷价差,效果也不错。
上层约束里最容易漏掉的是馈线容量约束:
Σ_i P_ev(i,t) ≤ P_feed(t) - P_base(t)
别小看这个式子,很多“全体车扎堆半夜充电”的反直觉结果,根子就是忘了它——下层用户会天然涌向最便宜的时段,如果没有总容量上限,所有车都挤在同一小时充,反而制造出新的峰值。这个我后面第六节还会详细说。
2.2 下层模型:电动车用户的充电决策
下层模型描述“一辆车收到价格信号后怎么充”。这里有个关键点:下层不是集中控制所有车,而是每辆车独立决策,所以下层其实是N个独立的小优化问题,每个问题结构相同、参数不同。
以单辆车为例,给定电价序列λ(t),用户的决策变量是各时段充电功率P(t),目标是最小化充电费用:
min Σ_t λ(t)·P(t)·Δt
但单纯最小化费用会导致“能少充就少充”,所以通常会加一个充电需求约束来兜底,比如离开时必须达到目标电量。核心约束我列一下:
- 电池SOC递推:
SOC(t+1) = SOC(t) + η·P(t)·Δt / E_bat,η是充电效率,E_bat是电池容量 - 电量上下限:
SOC_min ≤ SOC(t) ≤ SOC_max,一般取0.1和0.9左右 - 离开电量约束:
SOC(T_dep) ≥ SOC_target,这是用户的核心诉求 - 充电功率上限:
0 ≤ P(t) ≤ P_max,取决于充电桩类型,交流桩一般7kW,直流快充可以到60kW以上 - 充电时段:
P(t)>0只允许出现在[t_arr, t_dep]区间内
如果目标函数和约束都是线性的,下层就是一个线性规划,非常好解。如果你想做得更真实,可以在目标里加上电池退化惩罚项,写成二次函数,下层变成二次规划,求解也不难,但KKT转化时要注意凸性。
2.3 上下层的耦合变量与问题整体结构
上下两层靠什么耦合?这取决于你的调度机制是“直接型”还是“价格型”。
直接型调度里,上层直接给每辆车下达充电功率指令P_ev(i,t),下层在收到指令后评估自己的费用是否可接受,如果可接受就执行,不可接受就反馈一个自己的期望曲线——这种更像协商机制,需要额外的交互协议。
价格型调度更常见,也是我推荐的入门路线:上层决策各时段电价λ(t),下层每辆车以λ(t)为参数求解自己的充电计划,再把所有车的计划汇总成总充电负荷,反馈给上层用于评估削峰填谷效果。上层的决策变量是价格曲线,下层是充电功率曲线,二者通过“价格→需求”这个经济学关系耦合。
价格型双层问题的完整结构可以写成:
上层:min 负荷方差(或峰值) 决策变量:λ(t) 嵌套下层:min 用户充电费用 决策变量:P(i,t) 约束:馈线容量、价格上下限这个结构写清楚之后,建模阶段就结束了,剩下的问题是“怎么解”。双层问题一般是非凸的,不能直接扔给求解器,必须做转化或者设计迭代算法,这是整篇文章技术含量最高的部分。
3. 求解路线:KKT转化、迭代博弈与智能算法
3.1 KKT条件把双层压成单层:原理与前提
求解双层优化最硬核的路线,是把下层问题用它的KKT条件替换掉,从而把一个双层问题改写成单层问题。核心原理很直观:如果下层问题是一个凸优化且满足约束规格(比如Slater条件),那么“某点是最优解”等价于“该点满足KKT条件”。于是我们不再需要显式地“先解下层再代入上层”,而是把下层的最优性条件当成一组约束塞进上层模型,让上层一次性找到同时满足自己目标和下层最优性的解。
以线性下层为例,KKT条件包含四块:
- 原始可行性:下层所有不等式约束成立
- 对偶可行性:所有拉格朗日乘子非负
- 平稳性:下层目标函数的梯度与约束梯度的线性组合为零
- 互补松弛条件:每个不等式约束与其对偶乘子乘积为零
前三块都是线性的,麻烦在第四块——互补松弛条件是非线性的乘式。常用的处理手段是Big-M法:对每个不等式约束引入一个0-1变量z,把乘子×松弛量=0拆成两条线性不等式,其中M要取一个足够大但不至于引发数值问题的上界。转化之后,如果上层目标是二次的负荷方差,整个问题就变成一个混合整数二次规划(MIQP),可以用MATLAB里的intlinprog,或者更好用建模工具箱加商业求解器。
这里必须强调前提:KKT替换只在“下层是凸问题”时严格成立。如果你的下层目标里加了非凸的电池寿命惩罚,或者约束里有0-1变量,KKT只是必要条件不是充分条件,直接替换会丢掉真正的全局最优解。我在这个坑上栽过,后来对非凸下层一律改用迭代法或者枚举验证。
3.2 迭代式主从博弈:实现简单但收敛看运气
KKT转化虽然漂亮,但对数学基础不扎实的同学来说,写出所有互补约束的Big-M形式很容易出错。我建议先走迭代路线,把程序跑通、理解透之后再上KKT。
迭代的思路就是模拟主从博弈过程:
- 给电价曲线
λ(t)一个初始值,比如就用原始的峰谷电价 - 把所有车的下层问题逐个求解,得到每辆车的充电曲线,汇总出总充电负荷
- 把总负荷代进上层问题,求解一个给定负荷下的上层优化,更新电价或调度指令
- 重复2和3,直到电价或负荷曲线不再明显变化
这个做法本质上是一个不动点迭代,实现起来非常快,几行循环就能跑起来。但我要提醒:它没有收敛保证,遇到某些参数设置会震荡,表现为电价在两组值之间反复横跳,负荷曲线忽高忽低。我在实测里发现,给电价加上步长限制(每次迭代最多变化10%)能有效缓解震荡,但本质上还是在“赌”问题性质。
如果迭代不收敛,可以换智能算法的思路:外层用粒子群(PSO)搜上层决策(比如把24个时段的电价编码成一个24维粒子),内层对每个粒子固定电价后解N个下层LP,把下层响应代回上层目标作为适应度。PSO不依赖问题凸性,收敛情况比直接迭代稳一些,代价是慢——每个粒子都要解一遍N个LP,种群30个个体迭代100次,就是3000轮下层求解,小算例无所谓,EV数量上百之后非常酸爽。
3.3 MATLAB建模环境与求解器搭配
代码实现层面,我非常推荐用开源的建模工具箱YALMIP来搭双层模型。YALMIP最大的好处是把“建模”和“求解”解耦:你用sdpvar声明变量、写约束和目标,然后调用solvesdp,底层求解器你自己指定。这意味着你写KKT转化的代码时,不需要关心具体求解器的语法,想换求解器只改一行。
具体到求解器搭配,我给出一个经验表格:
| 下层问题形式 | 转化后整体问题 | 推荐求解途径 |
|---|---|---|
| 线性规划 | 混合整数二次规划(MIQP) | 商业求解器;小规模可用intlinprog |
| 二次规划 | 混合整数二次约束规划(MIQCP) | 支持MIQCP的商业求解器 |
| 非凸问题 | 无法严格KKT转化 | 外层PSO,内层fmincon或YALMIP |
| 大规模(EV>100) | 混合整数规划 | 商业求解器是唯一现实选择 |
个人经验:小算例(比如10辆车、24个时段)用MATLAB自带求解器完全够,跑一次几十秒;一旦变量超过几千个,自带求解器会慢到怀疑人生,这时候商业求解器和KKT转化的价值就体现出来了。不过那些求解器都是有授权费用的,学生项目可以考虑先用自带工具把逻辑跑通,再视需要迁移。
4. MATLAB代码架构:从数据生成到结果可视化
4.1 整体文件组织与仿真参数设计
代码组织这件事,看似不起眼,实际对后期调试影响巨大。我经历过一段所有代码堆在一个脚本里的黑暗时期,后来花半天时间重构,效率翻倍。推荐的文件结构如下:
main.m:主入口,定义全局参数、调用各模块、出图gen_ev_data.m:生成电动车数据(到达时间、离开时间、初始SOC、电池容量等)solve_lower.m:给定电价,求解单辆车的下层充电问题solve_upper.m:给定负荷响应,求解上层目标(用于迭代法)solve_kkt.m:走KKT转化路线时,构建并求解单层问题plot_results.m:绘制负荷曲线、电价曲线、充电功率热力图
仿真参数的设计直接影响结果的可信度,我常用的基准参数如下:
| 参数 | 取值 | 说明 |
|---|---|---|
| 调度周期 | 24小时,时段间隔15分钟或1小时 | 间隔越小模型越精细,求解越慢 |
| 电动车数量 | 20~50辆 | 入门建议20辆以内 |
| 电池容量 | 40~60 kWh | 按主流家用电动车取值 |
| 初始SOC | 0.2~0.5均匀随机 | 反映用户到家的剩余电量 |
| 目标SOC | 0.9 | 出门前期望电量 |
| 充电功率上限 | 7 kW(交流) | 也可设部分快充桩60kW |
| 充电效率η | 0.9 | 考虑线路和车载充电机损耗 |
| 基础负荷 | 某典型居民区日负荷曲线 | 晚高峰在18~22点 |
生成电动车数据时,到达时间建议用正态分布模拟晚归规律,均值放在18点到20点,标准差一两个小时。这里有个很容易被忽略的点:一定要用rng(固定种子)固定随机数种子,否则每次跑出来的车辆数据都不一样,实验结果没法复现,写论文时对比图表也没法自圆其说。
4.2 上层调度模型的核心代码
先展示KKT转化路线下,上层目标在YALMIP里的写法。假设车数N、时段数T已经定义好,双层问题里上下层的变量一起声明:
% 上层变量:每辆车每个时段的充电功率,以及电价序列 P_ev = sdpvar(N, T, 'full'); lambda = sdpvar(1, T, 'full'); % 上层目标:负荷方差最小化 P_base = load_profile; % 1 x T 基础负荷 P_total = P_base + sum(P_ev, 1); objective_up = sum((P_total - mean(P_total)).^2); % 上层约束:馈线容量与电价上限 constraints_up = [sum(P_ev, 1) <= P_feed_cap - P_base; ... 0 <= lambda <= lambda_max];这只是上层的一半,另一半是把所有车的下层KKT条件作为约束追加进来。比如第i辆车的SOC递推、功率上下限、离开SOC要求,每个约束都要配上对应的对偶变量,再写互补松弛的Big-M线性化。代码写起来比较长,我一般会封装成一个函数lower_kkt_constraints(P_ev, lambda, ev_data),在main.m里循环调用for i = 1:N,把返回的约束数组一次性拼进总约束集合。拼完之后调用商业求解器,一次求解得到电价和所有车的充电曲线,这是最干净的做法。
4.3 下层用户响应模型的代码实现
迭代法路线里,下层模型是最容易独立验证的模块。给定电价lambda,一辆车的充电优化在YALMIP里这样写:
function P_sol = solve_lower(lambda, ev) % ev 包含:电池容量E_bat、最大功率P_max、到达时段t_arr、离开时段t_dep、 % 初始SOC、目标SOC、充电效率eta、时段间隔dt T = length(lambda); P = sdpvar(1, T, 'full'); SOC = sdpvar(1, T, 'full'); objective = lambda * P' * dt; % 充电费用最小化 constraints = []; constraints = [constraints, SOC(1) == ev.SOC_init]; % 初始SOC for t = 1:T-1 constraints = [constraints, ... SOC(t+1) == SOC(t) + ev.eta * P(t) * dt / ev.E_bat]; end constraints = [constraints, SOC(t_dep) >= ev.SOC_target]; constraints = [constraints, 0 <= P <= ev.P_max]; % 功率上下限 for t = 1:T if t < ev.t_arr || t > ev.t_dep constraints = [constraints, P(t) == 0]; % 到达前离开后不充电 end end optimize(constraints, objective); P_sol = value(P); end注意SOC递推我是用循环写的,虽然YALMIP也支持向量化写法,但对新手来说循环更不容易出错。效率上,单个EV的LP求解毫秒级,迭代法里几十辆车几百轮循环完全撑得住。
我调试下层模型时习惯先用暴力网格搜索验证:给定一个简单电价,穷举所有可能的持续充电时长和充电起始时段,列出所有可行方案的费用,跟优化结果比对。几次下来就能确认SOC递推公式没写反、Δt没漏乘、时段索引没偏一位。
4.4 结果输出与负荷曲线对比
代码跑通之后,最重要的就是出图。我做仿真报告时固定出三张图,基本能覆盖所有评审关心的信息:
第一张是负荷曲线对比图:横轴24小时,纵轴功率,画三条线——原始基础负荷、单层优化后的总负荷、双层优化后的总负荷。这张图能直观看出削峰填谷的效果,也是论文里最常用的主图。实测里双层优化后的曲线往往比基础的晚高峰削掉一截,但会在凌晨出现一个次高峰,这是用户响应低价的结果。
第二张是电价曲线和总充电负荷的双纵轴图:一个轴画电价,另一个轴画充电总功率,能看出价格信号和负荷响应的因果关系——电价低的地方负荷鼓起来,用户“追着便宜电价跑”的行为一目了然。
第三张是充电功率热力图:横轴时段,纵轴车辆编号,颜色代表功率大小。这张图用来检查有没有车在离谱时段充电,比如凌晨四点才开始充电的车是不是真的合理,有时候能一眼看出约束写漏了。
数据导出方面,我习惯把所有结果存成表格(.mat或.csv),包括每辆车的费用、总负荷方差、峰值负荷这些指标,方便后面做参数敏感性分析时统一统计。
5. 调试与验证:不跑几遍真发现不了的坑
5.1 收敛失败:互补松弛条件与Big-M的冤案
我第一次把KKT转化写出来,信心满满地跑,结果求解器直接报“infeasible”。查了半天,问题出在Big-M的取值上。
互补松弛条件线性化时,要对每个“不等式约束×对应乘子=0”引入一个二进制变量z,然后写两条不等式:
松弛量 ≤ M1·z对偶乘子 ≤ M2·(1−z)
M1和M2的取值是个精细活。取小了,真实的可行解被排除在松弛化空间外,模型直接无解;取大了,虽然解还存在,但数值条件变差,求解器容易在分支定界里磨蹭半天甚至误判。我的经验是M的取值要结合问题的物理量级来估计——比如功率约束对应的M取最大功率的2倍,SOC约束的M取1(SOC本来就是0到1的量纲),别所有约束都用同一个M。这个细节我记得特别清楚,因为那次我所有约束都写了M=1000,SOC约束的乘子量级在0.01左右,数值上等于把所有互补条件全放开了,模型自然乱套。
另外,如果你用的是自带求解器,遇到“constraints not supported”这种错,大概率是目标函数带了二次项但求解器不支持MIQCP。这时候有两个选择:把上层方差目标线性化,用一个辅助变量z让z ≥ Σ(...)再最小化z;或者换一个支持MIQCP的求解器。前者简单可靠,我日常都用它。
5.2 结果“反直觉”:用户全体扎堆最优时段的排查
调试过程中最考验判断力的场景是:模型顺利求解了,但结果怎么看怎么不对劲。我印象最深的一次,所有电动车都在凌晨两点到三点充电,充电负荷曲线变成一个巨大的尖峰,比优化前还难看。
第一反应是怀疑下层模型坏了,但单独验证单辆车的行为,逻辑完全正确——两点到三点电价最低,用户当然往那里冲。问题出在上层约束:我只约束了每辆车自身的功率上限,忘了在总负荷上叠加馈线容量约束。下层用户是分散决策的,每个人都不会主动回避“大家都便宜”的时段,如果没有上层对这种集体行为设限,结果必然是从众扎堆。这其实恰恰说明双层模型是对的:用户的自利行为导致了群体性新峰值,上层需要对此负责。把馈线容量约束加回去之后,求解器会自动在低价时段里挑出部分车挪到次低价时段,曲线就合理了。
另一个常见反直觉结果是“电价明明设了峰谷,用户却完全不理睬”,所有车一回家就立刻充满。这种情况十有八九是下层约束太紧:比如目标SOC设得过高,或者t_dep太早,导致用户必须一插上就充才能按时充满,根本没有响应价格的自由度。处理办法是检查参数组合,看看是否存在“不同充电策略都满足约束”的冗余空间——如果参数本身就只留了一条路,那结果僵化是必然的。
5.3 先用小算例验证模型正确性
我强烈建议,不管你最终要跑多大规模,第一步先用小算例把模型逻辑验证透了再上规模。所谓小算例,就是3辆车、6个时段这种能手工验算的程度。为什么?因为规模大了以后,很多错误会被平均效应掩盖,你看结果只觉得“好像不太对”,但不知道哪里不对。而6个时段时,你可以手工枚举出每辆车的可行充电区间,把最优方案算出来跟模型输出对比。
我自己的验证清单是这样的:
- 先把下层模型单独拿出来,固定一组电价,用网格搜索法验证单辆车的最优解,确认SOC递推、功率上限、离开时间约束全部正确
- 再加一层,把几辆车的响应汇总,输入给上层,验证上层目标函数计算是否正确
- 最后才把双层闭环起来,用KKT转化和迭代法各跑一遍,对比两种方法的结果是否一致
两种独立的求解路线结果一致,这个双重验证基本能说明模型没写错,也顺带验证了收敛性。如果两套结果不一致,那要么是KKT转化有数学漏洞,要么是迭代法没收敛,排查方向就非常明确了。
6. 从仿真走向工程:扩展方向与落地思考
6.1 引入不确定性:从确定性模型到场景法
静态双层模型有个理想化假设:车辆数据(到达时间、初始SOC)是已知的。真实世界不是这样——谁几点回家、还剩多少电,只能预测不能预知。工程化改进的方向是做多场景随机优化:采集一段历史数据,生成若干个等概率场景,每个场景包含一组车辆数据,上层目标写成所有场景的期望负荷方差,决策变量是所有场景共用的电价曲线或者各场景各自优化的充电计划。
这样做之后,问题规模会线性膨胀,EV数量从20变成20乘以场景数(比如20个场景就是400辆车的下层问题)。我实测下来,30个场景以内KKT转化后的MILP还是能接受的,再多就要考虑场景缩减或者采样近似。另外一个更工程化的路线是滚动时域调度:不用一次把24小时算完,而是每小时滚动一次,只优化未来4到8小时,决策窗口短了,不确定性影响自然小很多,还能每步用最新数据修正。
6.2 电池退化成本与V2G扩展
如果你想做电动汽车到电网(V2G)方向的调度,就得在模型里增加放电决策,充电功率从0 ≤ P ≤ P_max变成-P_d_max ≤ P ≤ P_max。但只放开放电维度远远不够,因为电池循环寿命是有限的,车主的电池衰减成本必须建模进去,否则上层会为了削峰让用户深度放电,用户实际是亏的。
我在下层目标里加退化成本的做法是:给每次充放电循环一个等效成本系数,目标函数变成充电费用加退化损失,写成二次函数。这样下层从LP变成凸QP,KKT转化仍然有效,只是对偶变量维度和互补约束数量变多。仿真结果会告诉你一个扎心的结论:在目前电池成本下,绝大多数场景里V2G削峰带来的收益小于电池衰减成本,用户不应该深度放电。这个结论本身就有价值,比单纯吹V2G有多好要扎实得多。
6.3 从论文到落地:代码的可复现性与参数说明
最后给正在写论文的同学一个建议:仿真实验的代码和参数,整理清楚到“别人拿到就能跑”的程度。我在审稿和带学生过程中看过太多论文,图表很漂亮,但正文里不写参数取值,关键约束公式下标和变量定义含糊,复现的时候靠猜,非常劝退。我自己整理代码的习惯是,在main.m开头写一个参数注释块,包含所有数值和随机种子;模型里每个约束都用英文注释标明对应正文公式的编号;跑完之后自动导出结果表格。这样不管是自己隔三个月回来看,还是交给别人复现,都能快速接上。
结束语:几句掏心窝的经验
做这个研究方向几年下来,如果说只能分享三条经验,我会说:第一,建模之前先想清楚谁是leader谁是follower,想不清楚就画一张决策流程图,别上来就写目标函数;第二,任何双层模型都要先单独验证下层,下层错了上层再漂亮也没用,相当于地基歪了楼再高都是危楼;第三,仿真实验一定要固定随机数种子,对比实验才可复现,否则你会被自己昨天跑出来的结果和今天跑出来的结果不一致逼疯。
最后再分享一个小技巧:调参阶段把电价上限和下限的差值(也就是价格激励强度)设成一个变量,通过扫描这个变量画一条“价格激励强度—负荷方差下降幅度”曲线。这条曲线的拐点会告诉你,价格调度到底有多大空间、用户响应到什么程度就饱和了。这个图放到论文里,信息量比十张负荷曲线对比图都大,而且能直接支撑你“建议采用多大峰谷价差”的结论。调参调到这里,你的双层优化调度模型才算真正有了灵魂。