电动汽车集群并网的调度问题我接触了好几年,从最开始的确定性优化做到后来的随机规划,最后落到分布式鲁棒优化上。说句实话,这个方向的论文和代码实现虽然不少,但很多代码拿到手根本跑不通,或者跑通了你也不知道每一步在干什么。今天借这个题目,把一套完整的分布式鲁棒优化调度模型思路和Matlab实现的关键环节拆开来讲,不整虚的,直接说清楚模型怎么建、算法怎么设计、代码怎么写才不掉链子。无论你是刚上手新能源调度的研究生,还是已经在做电动汽车聚合控制的工程师,这篇东西应该都能省你不少折腾的时间。
1. 先搞清楚问题:电动汽车集群并网调度的场景与难点
1.1 场景描述与目标拆解
电动汽车集群并网调度,本质上要回答的问题就是:一个区域的电动汽车在接入电网之后,充电桩怎么分配功率,才能既满足车主的使用需求,又不让电网过载,同时还能让各方利益最大化——可能是运营商的收益最大,也可能是系统运行成本最小,还可能兼顾用户充电费用最低。
传统做法通常把电动汽车看成一个固定的负荷,按照预测值去做日前调度。但现实根本不是这么回事:车主几点插枪、几点拔枪、初始电量多少、到底充到百分之多少走,全都是不确定的。再加上如果并网规模大了,比如一个商业区停车场有几百辆电动车,集中调度的计算量大不说,单点决策的风险也高。
这里面有个非常大的坑,就是很多人把"集群"理解成"把车加起来当成一个大电池"。这个理解方向对了一半,但忽略了每辆车其实是独立的用户约束和电池约束。后来我逐步摸索,发现更合理的做法是把集群看作一个聚合体,但聚合的时候要保留每辆车的物理约束边界,而不是简单的容量相加。
1.2 不确定性的来源与量化方式
调度模型里最头疼的就是不确定性。在电动汽车参与电网互动的场景中,不确定性主要来自三个方面:
- 基础负荷的不确定性:区域内除了电动汽车之外的其他用电负荷。
- 可再生能源出力的不确定性:如果配套建设了光伏或风电,出力随机性很强。
- 电动汽车用户行为的随机性:到达时间、离开时间、初始电量、目标电量,这些参数在调度的时候只能估计,不可能精确预知。
这些不确定性如果不处理,最直接的后果就是:调度方案在纸面上"最优",实际执行时天天越限。用统计语言说,就是约束被违反的概率很高。要应对这个问题,常见的技术路线有三类:随机规划(Stochastic Programming)、机会约束规划(Chance-constrained Programming)和鲁棒优化(Robust Optimization)。
前两类需要假设随机变量的概率分布,这在很多情况下是不现实的——你很难精确知道一辆车明天早上8点01分到还是8点15分到。鲁棒优化则不需要精确分布,只需要给出不确定性集合,保证最坏情况下约束都不被破坏。但纯鲁棒又太保守,所有情况都按最坏的来,成本高得离谱。分布式鲁棒优化(Distributionally Robust Optimization,DRO)正好卡在中间:用矩信息或者Wasserstein距离来定义不确定性集合,既不需要精确分布,又不像纯鲁棒那么保守。这套东西放在电动汽车集群调度里,非常合适。
2. 鲁棒优化与分布式的结合点:为什么这个模型能应对不确定性
2.1 从集中式到分布式的必然性
早期做电动汽车调度的模型基本是集中式的:一个中央控制器收集所有车辆信息,建一个大优化模型,一次求解出所有车辆的充放电功率。这个方法在车数量少的时候没问题,但集群规模上来之后,集中式的毛病就暴露出来了:
- 通信压力大:每辆车都要实时把状态上报给中央控制器。
- 计算压力大:优化变量数量随车辆数线性增长,约束条件也是。几百上千辆车的时候,求解时间直接失控。
- 隐私保护差:用户的行车数据、用电数据被集中处理,这在商业场景中是很难被接受的。
- 单点故障风险:中央控制器一挂,整个调度系统瘫痪。
分布式优化的思路是:把整个调度问题分解成若干个规模较小的子问题,每个子问题对应一个子集群或者一个聚合商,各子问题之间只需交换少量边界信息,通过迭代逼近全局最优。这样通信、计算、隐私、可靠性四个方面都有明显改善。
2.2 分布式鲁棒优化的核心思想与模型架构
分布式鲁棒优化,可以拆成两个词来看:鲁棒性和分布式。
鲁棒性体现在目标函数和约束条件要能应对不确定性。这里常用的策略是构建两阶段鲁棒模型:
- 第一阶段(此处决策):在不确定性还没实现前做的决策,比如日前签订的购电计划。
- 第二阶段(等待与调整):不确定性实现后,通过实时调整来消除偏差,比如日内根据实际情况调整充电功率。
两阶段模型写出来的数学形式是 min-max-min 结构:外层最小化成本,内层最大化最坏情况下的调整成本,最内层最小化调整量。这个结构在很多论文里都见过,但真要在Matlab里写出来,嵌套求解器是跑不动的,需要用列与约束生成算法(C&CG)或者Benders分解来解。
分布式则体现在求解架构上。常用的是交替方向乘子法(ADMM)。这个算法现在已经是做分布式调度的标配了。它通过引入辅助变量,把耦合约束松绑到目标函数里,形成增广拉格朗日函数,然后交替求解各子问题并更新对偶变量,最终实现接近全局最优的收敛解。
这两个东西结合起来,问题就变成了:如何在ADMM的框架下,把鲁棒优化子问题嵌入进去。这个思路在理论上已经比较成熟,但从数学模型到Matlab代码实现,中间还有不少细节要处理,接下来这节详细说。
3. 模型构建细节:目标函数、约束条件与不确定性集合
3.1 目标函数怎么定才有实际意义
目标函数的选择直接决定优化结果的方向。在电动汽车集群并网调度里,常见的目标函数有以下几种,实际使用时你可以根据需求灵活组合:
| 目标类型 | 数学表达 | 适用场景 |
|---|---|---|
| 系统总运行成本最小 | min Σ(C_buy·P_buy - C_sell·P_sell) | 电网侧/运营商视角 |
| 用户充电费用最小 | min Σ(C_charge·P_charge) | 用户侧/充电服务平台 |
| 运营商利润最大 | max Σ((充电服务费+售电收益)-(购电成本+电池损耗)) | 商业运营场景 |
| 网损最小 | min Σ(P_loss,i) | 配电网运行场景 |
我做这套模型的时候,倾向采用系统总运行成本最小作为主目标,同时在目标函数中加上对电池损耗的惩罚项。因为电动汽车参与并网不只是充电,还要支持V2G(车辆到电网)放电。放电会增加电池循环次数,如果不把这部分损耗计入目标函数,模型会拼命放电以降低系统运行成本,算出来的结果根本不符合实际情况。
电池损耗惩罚项的形式,常见的是成本系数乘以放电量和充放电状态切换次数。成本系数可以根据电池寿命曲线和更换成本来标定,比如电池更换成本大概是800元/kWh,全寿命循环次数大约3000次,那么单次循环的损耗成本就是0.27元/kWh左右。这个系数放到目标函数里,能够有效抑制过度放电。
3.2 约束条件的工程化表达
约束条件是模型中信息量最大的部分。电动汽车集群调度的约束主要包括以下几个方面:
电动汽车电池状态约束
这是模型的核心约束之一,表示电池电量的动态变化:
% 荷电状态(SOC)递推关系 Soc(i, t+1) = Soc(i, t) + (eta_c * P_ch(i,t) - P_dis(i,t) / eta_d) * delta_t / Cap(i);其中 eta_c 是充电效率(通常取0.95),eta_d 是放电效率(通常取0.90),这两个效率的区别在于充放电过程有能量损耗,放电效率比充电效率低是普遍规律。
SOC还需要限制在安全区间:
Soc_min(i) <= Soc(i,t) <= Soc_max(i);用户出行需求约束:
% 离开时SOC必须达到目标值 Soc(i, T_dep(i)) >= Soc_target(i);这条约束非常关键,它保证了电动汽车不会因为参与调度而影响用户的正常出行。有些简化模型会把目标SOC设成和到达时一样,实际上是不合理的,会影响用户参与调度的积极性。
充电功率约束
每辆车的充放电功率都有上限,由充电桩功率限制和电池特性共同决定:
-P_dis_max(i) <= P(i,t) <= P_ch_max(i);注意 P_dis_max 和 P_ch_max 可能不一样。常规慢充桩的充电功率上限可能是7kW,但V2G放电功率上限可能是3.6kW,这取决于充电桩的硬件设计。
集群功率联络线约束
整个电动汽车集群从电网获取的总功率不能超过配电网的容量限制:
sum(P(i,t), i=1:N_ev) <= P_grid_max(t);这是电网安全运行的根本保障。如果不加这条约束,所有车同时开始充电,配电网必然过载。
3.3 不确定性集合的选择:从盒式到Wasserstein球
分布式鲁棒优化的关键就在不确定性集合的构建上。不同的不确定性集合带来不同的计算复杂度和保守度。
盒式不确定集是最简单的形式:
U = { ξ : ξ_min <= ξ <= ξ_max }它只限定随机参数的变化范围,不包含任何概率信息。优点是模型简单,可以转化为线性规划求解,缺点是极度保守。所有参数同时取最坏值,实际中几乎不会发生,但模型按这种情况配置资源。
椭球不确定集比盒式精细一些,引入了参数之间的相关性,但需要假设协方差矩阵已知。这在实践中同样很难获取准确数据。
基于Wasserstein距离的模糊集是目前DRO的主流选择。它的基本思想是:用历史数据构建一个经验分布,然后定义一个以经验分布为中心、半径为ε的Wasserstein球,真实施际分布被认为落在这个球内。这样模型不需要精确知道分布,只需要历史数据。
Wasserstein模糊集的优势在于:它同时考虑了概率分布的偏移和随机变量的取值范围,理论性质好,而且可以通过对偶变换把min-max问题转化为有限维凸优化问题。在Matlab中配合YALMIP或CVX求解时,转化后的模型结构比较清晰,实现起来不会太困难。不过,半径ε的选取需要经验。我一般用交叉验证的方法,把历史数据分段,在不同半径下跑模型,比较目标函数和约束违反情况,选出在鲁棒性和经济性之间平衡最好的值。
4. 分布式求解算法选择与ADMM实现要点
4.1 ADMM算法为什么适合这个场景
ADMM的数学基础是分解法(Decomposition)和增广拉格朗日法(Augmented Lagrangian Method)的结合。它解决的问题形式是:
min f(x) + g(z) s.t. Ax + Bz = c对应到电动汽车集群调度,本地决策变量 x 是各集群自己的充放电功率计划,全局耦合变量 z 是各集群从电网获取的总功率。耦合约束是各集群功率之和等于系统总功率。
ADMM的迭代格式是:
- x更新:固定z和拉格朗日乘子,各集群独立求解自己的子问题(这一步可以并行)。
- z更新:固定x和拉格朗日乘子,求全局变量的解。
- 对偶更新:更新拉格朗日乘子。
这个方法最大的优点是:子问题的规模小,可以并行计算;通信量小,只需传递集群总功率和拉格朗日乘子;迭代过程数值稳定,不需要二阶信息。
4.2 松弛因子与增广拉格朗日参数的选择
ADMM有一个超参数 ρ 需要手动设置。这个 ρ 是增广拉格朗日项中的惩罚参数,直接影响收敛速度和数值稳定性。我在实际调试中发现:
- ρ 太小,对偶残差更新缓慢,可能需要上千次迭代才能收敛。
- ρ 太大,原残差收敛快但对偶残差波动大,甚至出现震荡不收敛。
一个比较稳妥的做法是使用自适应惩罚参数方案。每隔若干次迭代,根据原残差和对偶残差的比值动态调整ρ:
if prim_resid > 10 * dual_resid rho = rho * 2; elseif dual_resid > 10 * prim_resid rho = rho / 2; end这个做法的直觉很简单:原残差太大说明罚得不够重,增加ρ;对偶残差太大说明罚得过重了,减轻ρ。实际跑下来,收敛速度比固定ρ快不少,而且不用反复试参数。
4.3 收敛判据的设置
判断ADMM迭代是否收敛,不能只看原残差。标准的判断条件是原残差和对偶残差都小于各自的容忍度:
% 原残差 prim_resid = norm(x_k - z_k, 2); % 对偶残差 dual_resid = norm(rho * (z_k - z_k_prev), 2);在实际工程中,我建议用相对残差而不是绝对残差。因为绝对残差受系统规模影响很大,100辆车和1000辆车的量纲完全不同。相对残差的做法是:
prim_eps = eps_abs * sqrt(n) + eps_rel * max(norm(x_k), norm(z_k)); dual_eps = eps_abs * sqrt(n) + eps_rel * norm(rho * z_k);eps_abs建议取1e-4,eps_rel取1e-3,具体数值根据你的数据量级微调即可。
4.4 分布式鲁棒子问题的求解工具选择
每个集群的子问题是个鲁棒优化问题。我实测下来,最顺手的组合是YALMIP + Gurobi或者YALMIP + CPLEX。YALMIP的鲁棒优化模块可以直接处理带不确定性的约束,内部会把问题转化为可解的鲁棒对等形式。
yalmip('clear'); % 决策变量 P = sdpvar(N_ev, T, 'full'); % 各车充放电功率 Soc = sdpvar(N_ev, T+1, 'full'); % 各车SOC状态 % 不确定参数(Wasserstein模糊集中的均值) mu = sdpvar(1, 1); % 目标函数 objective = sum(sum(cost_buy .* P)) + robust_penalty; % 约束 Constraints = []; % 鲁棒约束示例:DRO约束对偶形式 Constraints = [Constraints, ...]; % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 0, 'robust.lplp', 'duality'); optimize(Constraints, objective, ops);YALMIP的sdpsettings('robust.lplp','duality')选项会启用鲁棒对偶转化,这个还是很实用的。当然,如果你的问题规模特别大,也可以自己手动做对偶转化,把DRO问题转化为一个确定性的二阶锥或线性规划问题,然后再交给求解器。手动转化的速度更快,但推导过程比较繁琐,适合追求性能的人。
5. Matlab代码框架实战:从数据结构到迭代收敛判断
5.1 代码总体结构设计
实现这套分布式鲁棒优化调度模型,我不建议把所有代码堆到一个脚本里。虽然跑通没问题,但后续调试和改参数会非常痛苦。我自己的代码结构是分成几个模块:
EV_DRO/ ├── main.m # 主程序入口 ├── config/ │ ├── load_case.m # 加载系统参数(车辆数、时段、网络参数) │ ├── gen_ev_data.m # 生成电动汽车随机数据 │ └── uncertainty_set.m # 定义不确定性集合 ├── models/ │ ├── build_objective.m # 构建目标函数 │ ├── build_constraints.m # 构建约束条件 │ └── build_uncertainty.m # 鲁棒约束的构建 ├── algorithms/ │ ├── admm_solve.m # ADMM主循环 │ ├── subproblem_ev.m # 单个集群子问题 │ └── global_update.m # 全局变量更新 └── utils/ ├── plot_results.m # 结果可视化 └── compute_metrics.m # 指标计算这种模块化设计的好处是:你想换一种不确定性集合的构造方式,只需要改build_uncertainty.m;想换一种求解算法,只需改algorithms/目录下的文件,主程序的框架完全不用动。
5.2 数据初始化与电动汽车参数生成
电动汽车的数据生成看似简单,实际上对最终调度结果影响巨大。我见过很多人的代码直接生成一组均匀分布的随机数,这会让调度结果偏离实际很多。
合理的数据生成方式应该参考真实行为模式:
function ev_data = gen_ev_data(N_ev, T) % T是调度时段数,比如按15分钟一个时段,一天就是96个时段 % 到达时间:集中在早上8-9点(上班到达)和晚上18-20点(下班回家) arrival_peak1 = 8*4 + 1;% 8:00, 15min间隔 arrival_peak2 = 18*4 + 1; % 18:00 % 用混合高斯分布模拟到达时间 arrival_time = round(normrnd(arrival_peak1, 8, N_ev, 1)); % 初始SOC:正态分布,均值0.5,标准差0.15 init_soc = min(max(normrnd(0.5, 0.15, N_ev, 1), 0.1), 0.9); % 电池容量:不同车型不同,比如比亚迪汉77kWh,Model 3是60kWh,五菱宏光MINI只有13.9kWh capacity = rand(N_ev, 1) * 60 + 15; % 15~75kWh % 充电功率上限:慢充7kW,快充60kW,按比例混合 p_ch_max = zeros(N_ev, 1); fast_ratio = rand(N_ev, 1) < 0.3; % 30%的车辆使用快充桩 p_ch_max(fast_ratio) = 60; p_ch_max(~fast_ratio) = 7; ev_data.arrival_time = arrival_time; ev_data.init_soc = init_soc; ev_data.capacity = capacity; ev_data.p_ch_max = p_ch_max; end实际中你还需要考虑V2G的比例。目前支持V2G的充电桩还不算多,如果所有车辆默认都能放电,模型结果会偏理想化。
5.3 ADMM主循环的Matlab实现
下面给出ADMM主循环的核心代码框架,这是整个程序的核心部分,每一步我都加了注释,方便理解:
function [P_opt, Soc_opt, history] = admm_solve(ev_data, sys_data, rho_init) % 初始化 N_ev = ev_data.N_ev; T = ev_data.T; rho = rho_init; % 决策变量初始化 x = zeros(N_ev, T); % 各集群本地功率 z = zeros(1, T); % 全局变量 lambda = zeros(1, T);% 拉格朗日乘子 % 迭代参数 max_iter = 2000; eps_abs = 1e-4; eps_rel = 1e-3; historical = struct(); for k = 1:max_iter % 1. x-update: 并行求解每个集群的子问题 x_prev = x; for i = 1:N_ev x(i, :) = subproblem_ev(ev_data, sys_data, z, lambda, rho, i); end % 2. z-update: 全局变量更新 % 投影到可行域 [P_min, P_max] z_tilde = mean(x, 1) + lambda / rho; z = min(max(z_tilde, sys_data.P_min), sys_data.P_max); % 3. 对偶变量更新 lambda = lambda + rho * (mean(x, 1) - z); % 4. 计算残差 prim_resid = norm(mean(x, 1) - z, 2); dual_resid = norm(rho * (z - z_prev), 2); % 5. 自适应调整rho if mod(k, 10) == 0 if prim_resid > 10 * dual_resid rho = rho * 2; elseif dual_resid > 10 * prim_resid rho = rho / 2; end end % 6. 收敛判断 prim_eps = eps_abs * sqrt(T) + eps_rel * max(norm(mean(x,1)), norm(z)); dual_eps = eps_abs * sqrt(T) + eps_rel * norm(lambda); if prim_resid < prim_eps && dual_resid < dual_eps '收敛于迭代次数: ' + string(k) break; end z_prev = z; end end注意我这里的写法是单循环内部做N_ev次子问题求解。如果想发挥并行性能,可以用Parallel Computing Toolbox把子问题循环改为parfor,但要注意子问题之间的数据和变量不能有依赖关系。理论上各子问题之间是完全独立的,所以适合并行。
5.4 子问题求解的YALMIP实现
每个集群子问题的实质是:在给定全局变量z和拉格朗日乘子λ的情况下,求解一个鲁棒优化模型:
function [x_i] = subproblem_ev(ev_data, sys_data, z, lambda, rho, idx) % 集群idx的本地数据 T = ev_data.T; P_min = ev_data.P_min(idx, :); P_max = ev_data.P_max(idx, :); % 决策变量 P = sdpvar(1, T, 'full'); Soc = sdpvar(1, T+1, 'full'); % 目标函数:本地成本 + 拉格朗日惩罚项 local_cost = sum(ev_data.cost(idx, :) .* P); penal = lambda * z + (rho/2) * norm(P - z + lambda/rho)^2; objective = local_cost + penal; % 约束条件 C = []; for t = 1:T C = [C, Soc(t+1) == Soc(t) + (ev_data.eta_c * max(P(t),0) - max(-P(t),0)/ev_data.eta_d) * ev_data.dt / ev_data.Cap(idx)]; C = [C, Soc_min <= Soc(t+1) <= Soc_max]; C = [C, P_min(t) <= P(t) <= P_max(t)]; end % 离开SOC约束 C = [C, Soc(T+1) >= ev_data.Soc_target(idx)]; ops = sdpsettings('solver', 'gurobi', 'verbose', 0); result = optimize(C, objective, ops); x_i = value(P); end这里的max(P(t),0)是处理充放电方向的关键。P(t)>0表示充电,P(t)<0表示放电。使用max函数会让模型引入非光滑项,YALMIP会自动处理为线性不等式约束,不会影响求解。
6. 仿真算例设置与结果解读的关键环节
6.1 基准算例的搭建思路
仿真算例是验证模型有效性的核心。很多论文里的算例都太"漂亮"了,实际复现的时候你会发现各种问题。我的建议是,先从一个规模较小的算例开始验证代码正确性,再逐步扩展规模。
小规模算例建议:3个集群,每个集群10辆车,调度周期1天(96个15分钟时段)。这个规模足够验证ADMM的收敛性,又不至于让单次仿真耗时太长。
对比方案建议设置三组:
- 确定性模型:不考虑任何不确定性,直接用预测值做调度。这是基线方案。
- 盒式鲁棒优化:只考虑参数范围,不考虑概率分布信息。这是保守方案。
- 分布式鲁棒优化(DRO):本文模型。这是折中方案。
对比指标包括:
- 总调度成本
- 约束违反情况(有多少时段的实际功率超过了线路容量)
- 计算时间
- 迭代收敛次数
6.2 结果分析:三组方案的对比与解读
从我的仿真结果来看,一个典型的结论是:
| 方案 | 调度成本(元) | 实际执行时约束违反率 | 求解时间(秒) |
|---|---|---|---|
| 确定性模型 | 100%基准 | 约15%~20% | 2 |
| 盒式鲁棒 | 118%~125%基准 | 0% | 5 |
| 分布式鲁棒 | 106%~110%基准 | 小于1% | 30~60 |
这个结果非常能说明问题:确定性模型的成本最低,但执行时因为不确定性导致的约束违反率很高,这个方案实际中根本不可用。盒式鲁棒虽然保证了零违反,但成本高得离谱,用户在充电费用上难以接受。分布式鲁棒以微小的成本增加换来了约束的可靠性,而且在不确定性越大时,DRO的优势越明显。
这里有个非常实操的看结果技巧:一定要画出ADMM的收敛曲线(原残差和对偶残差随迭代次数的变化),确认收敛过程没有震荡,也没有陷入错误的稳定点。我遇到过的情况是,某些参数设置下残差前100次迭代下降很快,之后就停滞在很高水平,这说明子问题或者全局更新步骤有问题,而不是还没收敛。
6.3 灵敏度分析:不确定性半径ε的影响
不确定性集合的半径ε是DRO模型最重要的超参数。我的经验是,ε从0开始增加时,调度成本会单调上升,但上升速度会逐渐减缓,最终趋于一个稳定值。这个稳定值对应的是盒式鲁棒的保守程度。
在实际应用中,怎么选ε?我推荐的做法是:
- 使用历史数据,把预测误差的真实分布算出来。
- 设置一组ε候选值(比如0.01、0.05、0.1、0.2、0.5)。
- 用留一法(Leave-one-out)对每个候选值做验证,计算实际约束违反率。
- 选择约束违反率刚好低于可接受阈值(比如1%)的最小ε。
这样做的好处是,选出的ε有数据支撑,而不是拍脑袋定的。你在写论文的时候,这个流程本身就是一个很完整的分析闭环。
7. 调试心得与常见参数坑位
7.1 最容易踩的坑:储能SOC初值与目标值设置不当
这是我在代码调试中踩过最多次的坑。如果SOC初值设置得太高,比如0.9,而目标值也是0.9,那模型几乎没有调度空间,所有车都无法放电,整个V2G功能形同虚设。反之,如果初值太低,比如0.1,而目标值是0.9,那所有车必须持续充电,功率需求极高,可能导致线路约束全天满负荷。
合理的做法是,初值按照实际统计数据来,目标值设为比初值高10%~20%,同时给每辆车设置一个最大可调功率限值,保证调度有自由度。
7.2 充放电效率的处理方式
充放电效率如果不处理好,会产生一个严重的问题:模型会通过"充了再放、放了再充"来刷成本或刷收益。如果放电收益高,模型会在同一个时段内一边充电一边放电,形成循环,这是完全不符合物理实际的。
解决办法有两种:
- 引入充放电状态变量,用逻辑约束禁止同时充放电。这会引入整数变量,问题变成混合整数规划。
- 在目标函数中设置非对称价格,或者加上充放电切换惩罚项,从经济层面抑制循环。
第二种方法实现更简单,对求解速度影响也小。我会给放电加上一个固定的操作损耗,同时把放电收益设置得比充电成本低一些,这样循环操作无利可图,模型自然就不会去做了。
7.3 收敛问题的通用排查路线
ADMM不收敛,不要直接去调ρ,先按下面的顺序排查:
- 检查子问题是否正确求解,目标值是否在每次迭代中单调变化。
- 检查全局变量z的投影方向是否正确,可行域是否设置反了。
- 检查对偶变量λ的更新方向,这里是λ = λ + ρ*(Ax+Bz-c),符号错了必炸。
- 检查数据量级是否一致。比如电池容量是kWh量级,功率是kW量级,如果不统一到小时的量级,残差数值会差好多个数量级。
量级问题我单独提醒一次:Matlab的求解器对数值尺度很敏感。功率用kW、能量用kWh、时间用小时,那么SOC递推式里的 delta_t 应该用1(表示1小时)还是0.25(表示15分钟),要在初始化时统一好。如果不统一,可能表面上优化正常,实际上约束写得是错的。
7.4 与确定性模型的比较基准设置
写论文或者做报告时,和确定性模型比较是必须的,但有些人直接把确定性模型当成"理论最优解"来对比,这是不严谨的。严谨的做法是,确定性模型求解完成后,把得到的调度方案放到实际不确定性场景中去做蒙特卡洛模拟(通常1000次),统计真实的约束违反率和成本。这个过程也被称为鲁棒性评估。
function violation_rate = robustness_eval(P_schedule, uncertainty_scenarios, sys_data) N_scenarios = size(uncertainty_scenarios, 3); violation_count = 0; for s = 1:N_scenarios % 对每个场景,校验功率约束是否满足 actual_power = P_schedule + uncertainty_scenarios(:, :, s); if any(abs(actual_power) > sys_data.limit) violation_count = violation_count + 1; end end violation_rate = violation_count / N_scenarios; end这个评估过程其实不复杂,但非常重要。没有这步评估,前面的DRO对比就成了空对空。
我自己一路做下来最大的体会是,电动汽车集群调度这个方向,数学模型和代码实现之间隔着很多细节,论文里不会给你写清楚的细节。比如YALMIP的鲁棒对偶选项怎么设置、ADMM的ρ怎么自适应调整、SOC初值怎么生成才能保证模型有可行性——这些不亲手跑一遍,光看公式是完全体会不到的。希望这篇东西能帮你少走一些我走过的弯路,如果你在复现过程中遇到具体的报错或者不收敛的情况,欢迎在评论区把报错信息贴出来,我们一起排查。