电动汽车充放电最优调度:Matlab全局与局部两级优化实现
发布时间:2026/10/2 22:19:15来源:尧图网络
提到电动汽车充放电最优调度很多人的第一反应就是“给车安排个充电时间”但真正落过地的人都知道事情远没有这么简单——你要考虑用户什么时候插枪、什么时候拔枪、电价怎么波动、电池能扛多少次充放循环、变压器会不会过载甚至还要跟相邻的车辆和充电桩抢功率。更麻烦的是“全局最优”和“局部最优”这两者之间经常打架站在整个园区的角度看你希望车辆错峰充电、削峰填谷但站在单个用户的角度看人家就是下班回家插上枪、第二天满电走人你招呼他半夜起来挪车显然不现实。这个项目做的不是那种玩具级的小例子而是用Matlab把“全局到局部”这条调度链路完整走了一遍先做日前全局调度把整个充电集群一天24小时的充放电计划排出来再做日内局部修正把实时负荷、电压约束、临时插枪这些突发因素塞进去用滚动优化的思路逐段调整。写完这套代码之后最大的感受是调度的核心根本不是“算”而是“建模”——把物理约束翻译成数学约束把管理目标翻译成目标函数这一层想清楚了剩下的求解反而是最省力的事。这套实践的完整思路、代码框架、踩过的坑以及一批可以直接抄作业的Matlab实现细节我都整理在这篇里了。适合正在做新能源调度、微电网研究的学生也适合车企或充电运营商里做能量管理的工程师参考。1. 先从“为什么必须从全局做到局部”说起1.1 全局调度解决的是“昨天就知道的账”全局调度有一个特别典型的应用场景园区/充电站的日前计划。电网的分时电价通常是前一天公布的峰谷时段基本固定通勤规律也相对稳定——所以理论上你可以在前一天晚上基于对第二天电价曲线、基础负荷曲线、车辆到达/离开时间预测把每一个充电桩、每一辆车的充放电计划全部算出来。这就是“全局”的底气信息相对完整约束相对确定目标是让整个集群的综合成本最小。这时候的数学模型本质上是一个带约束的混合整数/线性规划问题。决策变量是每辆车在每个时间段是充电还是放电、功率是多少目标函数是总电费向上游买电的费用尽可能低同时尽可能不向电网倒送功率因为目前很多场景下倒送电并不赚钱反而可能触发计量和结算问题约束条件则包括每辆车的SOC上下限、到达离开时间窗口、充电桩功率上限、变压器容量上限等等。用Matlab的linprog、intlinprog甚至YALMIP接求解器都能搞定关键不在于求解器多强而在于约束建得够不够细。但全局调度有个天然软肋它依赖预测而预测一定不准。比如某天下午突然下雨、气温骤降园区基础负荷比预测高了10%或者有三辆车临时插进来本来下午两点走的车拖到五点才走——这些“当天才知道”的事情日前计划根本管不了。如果只靠全局调度遇到突发情况就只能硬扛要么超容量限功率要么牺牲用户的充电完成度电网侧和用户侧两头都容易闹意见。1.2 局部调度解决的是“现场才出现的事”局部调度也叫实时/滚动调度的思路刚好补上这个短板不试图一次性算完所有时间段而是把时间轴切成滚动窗口——每15分钟或者每小时基于当前的真实状态SOC、实时负荷、当前电价、在网车辆列表重新算一次未来1到2小时的充放电计划只执行当前窗口的第一段然后推进到下个窗口再算。这就是模型预测控制MPC的思想。局部调度最大的价值是能把“不可预知”变成“可响应”。比如实时负荷冲高了局部优化会自动压低充电功率甚至切换成放电保证变压器不过载再比如某辆车临时插入且马上要出发那就把剩余窗口里其他车辆的充电功率让渡一部分给它优先保证那辆车的SOC需求。这种修正能力是全局调度给不了的。项目里最终采用的方案是“全局定基准局部做修正”的两级架构全局调度先给出一套基准充放电计划作为参考局部调度在基准之上滚动调整并且把调整幅度做了惩罚约束避免系统因为微小扰动来回抖动。这套架构在工程上很常见行业术语叫“分层协调调度”下面是里面的执行结构。日前全局调度基于预测数据 → 生成24h基准计划步长15min/1h ↓ 下发基准计划 日内局部修正基于实时数据 → 在基准附近滚动优化窗口1h/步长15min ↓ 输出实际执行指令 充电桩/充电机执行功率指令反馈SOC与状态1.3 为这套方案选的Matlab技术栈Matlab在这个项目里承担了三件事建模、求解、可视化。建模靠的是矩阵化描述约束和目标函数求解用的是优化工具箱可视化主要靠plot和绘制充放电甘特图。坦率说Matlab的求解速度跟C和商用求解器原生环境没法比但胜在三个优势一是矩阵描述和论文里的数学公式几乎一一对应调错一目了然二是画图方便给导师或领导汇报时出图快三是验证算法非常灵活不用花精力处理底层数据结构。代码结构上我按“数据模块—模型模块—求解模块—结果模块”四个目录拆开主脚本只负责串联。尤其提醒一点不要把所有逻辑堆在几个几百行的脚本里调度问题涉及矩阵维度多、约束多一旦出错堆脚本会让你查到怀疑人生。项目里最终的文件结构大致如下。ev_scheduling/ data/ # 电价、负荷、车辆参数等输入数据 model/ # 目标函数与约束构建函数 solver/ # 全局调度与局部调度求解脚本 result/ # 结果输出与绘图脚本 main_globalsolve.m # 全局调度主入口 main_localsolve.m # 局部调度主入口2. 核心数学模型怎么搭目标函数与约束的细节2.1 目标函数不能只算“电费最低”很多刚上手的人会把目标函数直接写成“总电费最小”这在数学上没有问题但真正做工程不够用。原因很简单如果只盯电费在峰电时段系统会倾向于让所有车一起放电谷电时段又拼命充电不仅让电池循环次数快速消耗还会让局部时段功率曲线的波动非常剧烈对上级电网极不友好。所以实际建模时目标函数至少要放三项进去——购电成本、电池退化成本、功率波动惩罚。购电成本好理解就是各时段从电网买电的费用减去放电上网的收益如果有这个结算机制的话。电池退化成本是一项容易被忽略但很重要的部分锂电池的循环寿命和充放电深度、放电倍率直接相关如果局部调度每次都把电池以1C甚至更高倍率放干那么省下的电费不够换电池的。项目里用了一个工程上常用的简化模型——每次充放电循环的退化成本近似为固定值乘以放电量更精细的做法是考虑SOC区间和温度但第一版没必要。功率波动惩罚则是把相邻时段的功率变化量加进目标函数用很小的权重系数压制目的是防止系统频繁大幅调整指令。综合这三项后目标函数写成下面这样这是项目里实际跑通的形式其中每个符号都对应代码里的一个矩阵minimize sum( c_buy(t)*P_grid_buy(t) - c_sell(t)*P_grid_sell(t) ) alpha * sum( E_discharge(i) ) // 电池退化成本 beta * sum( |P_grid(t) - P_grid(t-1)| ) // 功率波动惩罚参数alpha和beta建议通过仿真调几轮再定不要拍脑袋。我在调试时的经验是先去掉后两项跑一次纯电费最优看功率曲线长什么样、SOC变化剧烈到什么程度再逐步加大alpha和beta直到功率曲线平滑、SOC曲线合理为止。这个过程很像给设备调PID参数多试几次才能找到手感。2.2 约束条件决定模型“像不像真的”全在这里目标函数决定优化方向约束条件决定可行性。这个项目里我用到的关键约束按“车辆侧—基础设施侧—系统侧”三层梳理如下你在建模时也可以按这个思路逐条查漏。车辆SOC连续性约束这是最基础的物理约束下一时段的SOC等于上一时段的SOC加上充电电量、减去放电电量扣除效率损耗同时不能超过电池容量对应的SOC上限、不低于用户设定的下限。充放电功率限值约束每辆车的充电/放电功率有上下限。电动车充电桩一般支持双向功率但额定功率不一定对称项目里用的数据是充电最大7kW、放电最大5kW这种不对称必须分别约束否则求解器按对称处理会得出不可行的答案。时间窗口约束车只有停在充电桩上的时间段内才能充放电。这个约束用一个大M法或者二进制变量来激活属于混合整数规划的处理范畴。如果用intlinprog每个时段、每辆车都要引入一个0-1变量标记“在网或离网”。变压器容量约束所有电动汽车充放电功率叠加园区基础负荷后不能超过变压器额定容量。这条约束直接把“单辆车问题”升级成了“集群问题”也是全局调度区别于单枪充电控制的根本所在——不是每辆车各算各的而是所有车共享一个总功率天花板。倒送电网约束可选部分地区不允许分布式资源向电网倒送功率否则影响台区电压稳定。这个约束直接令放电功率不超过当前本地负荷实现“放电不上网”。项目里默认放开倒送但代码里保留了这条约束的开关方便适配不同地区政策。所有约束最终都翻译成A*x b或Aeq*x beq的标准矩阵形式再交给求解器。这个翻译过程是Matlab实现里最繁琐也最容易出bug的部分后面会用具体代码展示如何处理。2.3 为什么不用粒子群而用优化工具箱我最初尝试过用粒子群PSO、遗传算法GA来做这个调度问题因为这两个算法在学术论文里出镜率极高感觉“AI味”很足。但实际跑下来发现两个问题一是可重复性差同样的输入跑两次解可能完全不同二是有约束条件时很难保证可行——粒子群这类启发式算法本质上是无约束搜索处理复杂的线性和整型约束需要额外加罚函数而且罚函数系数非常难调调不好就出现“看似收敛其实违反约束”的情况。相比之下Matlab优化工具箱里的linprog线性规划、intlinprog混合整数线性规划、fmincon非线性规划内部有成熟的约束处理机制给定一个数学上合理的问题描述求解速度快也稳定。项目里的全局调度最终用的是intlinprog因为涉及“车是否在网”的0-1变量局部调度则简化成线性规划短暂的不确定性用滚动窗口来消化。这里想传达的就一句话能用凸优化就别上启发式算法这条经验在调度类问题里几乎永远成立。3. Matlab实现要点从数据输入到求解的核心代码3.1 输入数据怎么组织矩阵优先逐时段展开先看数据准备这段。调度的基础是时间和车辆两个维度的数据矩阵我统一用15分钟作为最小时间粒度。假设预测周期是24小时那么时段数T 96假设园区有10辆电动车车辆数N 10。时间矩阵的每一列对应一辆车每一行对应一个时段值表示该时段的充放电功率正为充电、负为放电。代码里这样初始化%% 参数设置 T 96; % 15min一个时段24h共96个时段 N 10; % 参与调度的电动汽车数量 dt 0.25; % 时段长度单位小时 % 车辆参数 SOC_init 0.2 * ones(N, 1); % 初始SOC20% SOC_max 0.9 * ones(N, 1); % SOC上限90% SOC_min 0.2 * ones(N, 1); % SOC下限20% Cap 40 * ones(N, 1); % 电池容量40kWh eta_ch 0.95; % 充电效率 eta_dis 0.90; % 放电效率 P_ch_max 7 * ones(N, 1); % 最大充电功率7kW P_dis_max 5 * ones(N, 1); % 最大放电功率5kW % 分时电价单位元/kWh实际数据从外部Excel或mat文件读入 price_buy zeros(T, 1); price_buy(1:32) 0.35; % 00:00-08:00 谷段 price_buy(33:56) 0.85; % 08:00-14:00 平段 price_buy(57:72) 1.25; % 14:00-18:00 峰段 price_buy(73:88) 0.85; % 18:00-22:00 平段 price_buy(89:96) 0.35; % 22:00-24:00 谷段车辆到达和离开的时间窗口我用一个0-1矩阵Avail表达——第t行第i列为1表示第i辆车在第t个时段停在站内可以充放电为0表示不在。这个矩阵是全局调度和局部调度共同的核心输入它决定了哪些时段哪些车能参与调度。Avail zeros(T, N); % 比如第1辆车从第17时段(04:15)到第84时段(21:00)在网 Avail(17:84, 1) 1; % 第2辆车从第33时段(08:15)到第60时段(15:00)在网 Avail(33:60, 2) 1; % 其余车辆按类似方式初始化3.2 求解器接口怎么调一个跑通的intlinprog例子下面这一段是全局调度的核心求解代码。为了把约束都包装成标准形式我需要把决策变量排成一个长向量前T*N个变量是充电功率非负紧接着T*N个是放电功率非负再接着T个是向电网购电功率最后再加一个总功率曲线的辅助变量用来表达波动惩罚。这样排列是为了把充放电、购电功率都纳入同一个线性约束体系。%% 决策变量组织 % x [P_ch(1..T,1..N); P_dis(1..T,1..N); P_grid(1..T)] n_ch T * N; n_dis T * N; n_grid T; n_total n_ch n_dis n_grid; %% 构造目标函数系数向量 f zeros(n_total, 1); % 充电功率对应购电成本为正放电功率对应收益为负或电池退化成本 for i 1:N for t 1:T idx_ch (i-1)*T t; idx_dis n_ch (i-1)*T t; f(idx_ch) price_buy(t) * dt; f(idx_dis) -price_buy(t) * dt * 0.9 alpha * dt; % 放电收益打9折 退化成本 end end % 第1个时段引入的功率波动惩罚系数beta这里只在相邻时段差上作用在intlinprog里整数变量车在网或离线通过intcon参数指定。如果采用“离线时充放电强制为0”的大M约束可以不用为每辆车都设0-1变量而是把可用性矩阵直接乘到功率变量上限约束里这样问题会退化成纯线性规划速度更快。我实际用的是后一种思路给P_ch加的上限不是常数而是Avail .* P_ch_max——不在网的时候上限直接变成0求解器自然就不会安排充电了。这一招能把MILP问题变成LP问题计算规模小一个量级强烈推荐。%% 约束矩阵构造 A []; b []; Aeq []; beq []; % 约束1充放电功率上限含在网标志 % 用稀疏矩阵加速构造 I_ch_ub 1:n_ch; J_ch_ub 1:n_ch; V_ch_ub ones(1,n_ch); A_ub1 sparse(I_ch_ub, J_ch_ub, V_ch_ub, n_ch, n_total); b_ub1 Avail(:) .* repmat(P_ch_max, T, 1); A_dis_ub sparse(n_ch (1:n_dis), n_ch (1:n_dis), ones(1,n_dis), n_dis, n_total); b_dis_ub Avail(:) .* repmat(P_dis_max, T, 1);约束2是购电功率和充放电功率之间的功率平衡。它的含义是从电网买来的电一部分送去充电一部分供基础负荷车辆放电则作为额外电源加入。这个约束对每个时段都成立是连接所有决策变量最核心的一条等式约束。% 约束2功率平衡约束每个时段 % sum(P_ch(t,i)) P_base(t) P_grid(t) sum(P_dis(t,i)) % 即 P_grid(t) - sum(P_ch(t,i)) sum(P_dis(t,i)) P_base(t) Aeq_power zeros(T, n_total); for t 1:T for i 1:N idx_ch (i-1)*T t; idx_dis n_ch (i-1)*T t; Aeq_power(t, idx_ch) -1; % 充电是负荷 Aeq_power(t, idx_dis) -Aeq_power(t, idx_dis) - 0; % 占位 Aeq_power(t, idx_dis) 1; % 放电是电源 end Aeq_power(t, n_ch n_dis t) 1; % P_grid(t) end beq_power P_base; % 基础负荷曲线约束3是变压器容量约束。不仅充电功率放电功率通过变压器流回本地负荷时也占用容量路径所以要取功率绝对值之和不超过上限。为了保持线性我把这个约束拆成两个不等式充电侧和放电侧分别约束。% 约束3变压器容量约束每条馈线/变压器 A_trafo zeros(T, n_total); for t 1:T for i 1:N idx_ch (i-1)*T t; idx_dis n_ch (i-1)*T t; A_trafo(t, idx_ch) 1; A_trafo(t, idx_dis) 1; % 放电同样占用容量路径 end A_trafo(t, n_ch n_dis t) 1; % 购电也占用容量 end b_trafo P_trafo_max - P_base; % 变压器剩余容量最后把约束拼起来调用linprog求解。由于目标函数只有线性项没有把SOC的绝对值约束直接写成目标SOC上下限是用等式约束递推得到的已知初始SOC通过充放电功率累加得到每个时段SOCSOC上限和下限转化为对功率的线性不等式。%% 组装并求解 A [A_ub1; A_dis_ub; A_trafo]; b [b_ub1; b_dis_ub; b_trafo]; Aeq Aeq_power; beq beq_power; lb zeros(n_total, 1); ub []; % 上限已经在A_ub1和A_dis_ub中体现 options optimoptions(linprog, Display, iter, Algorithm, dual-simplex); [x_opt, fval, exitflag] linprog(f, A, b, Aeq, beq, lb, ub, options); % 解码结果 P_ch_opt reshape(x_opt(1:n_ch), T, N); P_dis_opt reshape(x_opt(n_ch(1:n_dis)), T, N); P_grid_opt x_opt(n_chn_dis(1:T));跑通之后重点检查三样东西一是exitflag必须为正或者显示Optimal solution found二看SOC曲线是否都在上下限内三看变压器功率有没有超限。任何一个不满足优先怀疑约束写错了而不是求解器问题。3.3 SOC递推与约束转换怎么处理SOC递推是调度模型里最容易写出bug的地方。正确写法是每个时段的SOC等于上一时段SOC加上充电电量乘效率、减去放电电量除以效率再除以电池容量。用矩阵形式组织起来可以避免写一堆循环。%% SOC递推矩阵构造 % SOC(t1,i) SOC(t,i) (eta_ch * P_ch(t,i) - P_dis(t,i)/eta_dis) * dt / Cap(i) % 但要注意SOC_0是初始值t从1开始 % 为了把SOC约束写成决策变量的线性不等式需要用累积矩阵 CumMat tril(ones(T, T)); % 下三角累积矩阵 % 充电电量累积矩阵 A_soc_ch zeros(T, n_ch); for i 1:N idx_cols (i-1)*T (1:T); % 利用kronecker积 block CumMat * (eta_ch * dt / Cap(i)); A_soc_ch(:, idx_cols) block; end % 放电电量累积矩阵 A_soc_dis zeros(T, n_dis); for i 1:N idx_cols (i-1)*T (1:T); block CumMat * (dt / (eta_dis * Cap(i))); A_soc_dis(:, idx_cols) -block; % 放电使SOC下降 end % SOC约束SOC_min SOC_init A_soc_ch*P_ch A_soc_dis*P_dis SOC_max A_soc_ub [A_soc_ch, A_soc_dis, zeros(T, n_grid)]; b_soc_ub SOC_max - SOC_init; % 这里每个t都对应SOC(t)但更准确需要在每个t约束 A_soc_lb -[A_soc_ch, A_soc_dis, zeros(T, n_grid)]; b_soc_lb SOC_min - SOC_init;不过要注意上面这样写只约束了每个时段的末值SOC而初始SOC也需要在第一个时段就被包含进去。更严谨的做法是把SOC_init追加到决策变量里或者把第一时段的SOC单独拎出来避免初始状态和递推状态产生歧义。项目里我用了一个更简单的招法把SOC作为额外状态变量放进求解结果中追踪但在约束上只用末段SOC达到目标比如离开时SOC大于某个值中间过程只在功率上限和容量约束上卡死。这样求解速度更快而且从工程意义上只要每时段的功率没超限SOC的变化就一定是连续的。3.4 局部滚动优化怎么实现局部调度部分比全局调度更灵活。真实车辆到达时间、实时SOC、当前电价都是实时更新的因此我把全局调度的求解器封装成一个函数solve_schedule(SOC_now, price_now, P_base_now, Avail_now, horizon)然后用一个循环按15分钟滚动推进。%% 局部滚动优化主循环 Horizon 8; % 滚动窗口长度8个时段2小时 Step 1; % 每1个时段滚动一次 T_real 96; % 实际运行时段 SOC_now SOC_init; % 当前实际SOC P_exec zeros(T_real, N); % 实际执行功率记录 for t 1:Step:T_real-Horizon1 % 取当前实际状态 avail_window Avail_real(t:tHorizon-1, :); price_window price_real(t:tHorizon-1); base_window P_base_real(t:tHorizon-1); % 调用局部求解器 [P_ch_win, P_dis_win, ~] solve_schedule(SOC_now, price_window, ... base_window, avail_window, Horizon); % 只执行第一个时段的指令 P_exec(t, :) P_ch_win(1, :) - P_dis_win(1, :); % 更新实际SOC考虑执行效率 SOC_now SOC_now ... (eta_ch * P_ch_win(1,:) / Cap - P_dis_win(1,:) / (eta_dis * Cap)) * dt; end局部优化的目标函数里我把全局基准计划作为参考项放进去让局部计划的功率尽量贴近全局计划的功率但允许一定范围内的偏差。这样既保证了突发情况下的响应能力又不会因为实时扰动的噪声太大导致整体计划面目全非。具体实现上是在目标函数里加一个二次项lambda * norm(P_local - P_ref)^2用quadprog而不是linprog求解速度依然足够快。4. 仿真案例与结果解读4.1 案例场景设定与参数配置为了让这套代码的验证结果更有说服力我设计了一个贴近实际的园区场景变压器容量500kVA基础负荷曲线呈典型的双峰形状早高峰和晚高峰10辆电动汽车类型分为两类——6辆私家车白天停在园区、下午到晚上离开和4辆网约车夜间回场充电、白天在外跑电价采用典型的分时电价峰段1.25元/kWh平段0.85元/kWh谷段0.35元/kWh。这个配置在长三角的工厂/办公园区非常常见。车辆参数方面私家车电池容量40kWh网约车电池容量60kWh初始SOC随机分布在20%到40%之间用户离场期望SOC设定为80%到90%。私家车的在网时间主要是工作日早8点到晚6点网约车则是晚8点到次日早6点。两拨车辆的“错峰补能”需求正好是检验调度算法能不能合理分配功率的绝佳场景。4.2 全局调度结果长什么样全局调度跑出来后最直接的结果是大功率曲线和每辆车的SOC曲线。无调度情况下即车辆到场即以最大功率充满园区变压器在晚高峰叠加充电负荷后大概率会突破500kVA的上限全局调度后充电负荷被大量搬移到谷电时段变压器最大负荷降到450kVA左右峰谷差明显收窄。从单车SOC曲线上还能看到一个有意思的现象部分私家车在下午13点到15点的峰电时段被算法安排了短暂放电——SOC从85%放电到78%然后到傍晚离场前又补回到90%。放电量不大但对削峰贡献不少而且对应的电池退化成本通过alpha参数控制在合理范围内最终综合成本比“无调度”和“傻瓜式延迟充电”两种方式都低。这正好印证了充放电一体调度的价值不只是调充电某些时段“放一点”反而经济性更好。全局调度还有一个输出值得关注购电功率的功率波动惩罚系数beta直接影响功率曲线的平滑度。beta0时功率曲线在峰谷切换点附近会出现阶梯跳跃beta0.002左右时曲线平滑度明显改善且对总成本的影响可以忽略。这个参数建议在实际项目里多扫几组值找到“平滑”和“省钱”的平衡点。4.3 局部调度在突发场景下的表现局部调度的验证我专门设计了两个突发场景。第一个是基础负荷骤升下午15点天气骤热导致空调负荷比预测高了100kW。全局计划的预期功率在15点时是300kW局部调度检测到实时负荷400kW后立刻把该时段充电功率压低了60kW并让部分车辆提前进入放电状态变压器总功率依然控制在495kW以内。如果只看这段曲线你几乎看不出异常因为局部修正把冲击“吃”掉了。第二个场景是车辆临时插队一辆没有在日前计划中的网约车在9点突然进场SOC只有15%要求12点前充到60%。局部调度把它插入到可用车辆列表里同时将原来三辆已完成60%SOC的私家车的充电功率小幅下调优先保障临时车辆。滚动窗口每15分钟重算一次到11点45分时这辆车SOC已经达到59.7%满足离开时的期望值。事实证明局部滚动优化对“插队”这类事件天然有包容性——这也是单独做全局调度做不到的。4.4 执行结果与理论解的偏差分析所有调度算法都要面对一个问题理论最优和实际执行总有偏差。在我这个案例中偏差来源主要是两个一是SOC反馈的精度二是执行机构的功率跟踪误差。前者的影响更大如果实时SOC反馈有5%的误差局部调度对车辆可用电量的判断就会出错导致要么过度放电触发保护要么充电完成后还有大量电量剩余。解决办法是在每个滚动窗口的初始用实时SOC重新定义约束边界而不是沿用日前计划里的SOC轨迹。我在这套代码里用一个SOC_init_real参数替代全局解给出的理论SOC确保偏差不会在滚动过程中越积越大。5. 常见问题排查与避坑经验5.1 求解失败的排查思路新手最常碰到的问题是linprog或intlinprog返回“problem is infeasible”。这个报错有几种常见原因按出现频率排列如下约束条件之间互相矛盾。比如变压器容量上限设得过低而又有车辆必须在某时段完成充电任何排班方案都会超限。这种问题要先检查变压器约束和充电需求约束是否自洽。SOC初始值没有和功率决策变量衔接好。最常见的是“初始SOC为20%但约束里要求第一个时段就达到80%”这在物理上不可能存在需要检查递推矩阵是否把初始值重复计算了。功率平衡约束的符号反了。比如充电功率列在等式右侧、放电功率列在左侧看起来没问题但求解器角度完全不同。建议先用一个只有一辆车、两个时段的微型case手算验证再说。排查的标准动作其实是先缩小问题把车辆数设为1、时段数设为4所有边界条件手动算一遍再把约束矩阵和手算结果对比。如果小case能跑通再逐步放大这样比在10辆车96个时段的大模型里瞎猜快得多。5.2 求解速度慢怎么办如果问题规模比较大比如几十辆车、几百个时段linprog性能可能成为瓶颈。我实际测试过10辆车、96个时段的线性规划问题在普通笔记本上不到1秒就能解完但如果升级到混合整数规划每辆车每个时段都有二进制变量求解时间可能成几十秒甚至几分钟。所以能让问题保持线性的部分尽量不要引入整数变量。另外使用optimoptions里的Algorithm选项也很讲究。dual-simplex在约束规模大时通常比默认的内点法更快且在重新求解一系列类似的滚动优化问题时即热启动优势更明显。局部调度反复调用linprog用dual-simplex配合起点的x0输入实测可以把整个滚动过程的总耗时压到几秒以内。5.3 数据输入常见的坑电价、负荷、车辆参数这些输入数据的单位不统一是我在项目里几乎每个阶段都会踩的坑。电价是元/kWh功率是kW电池容量是kWh时间粒度是小时——看起来都是“电力常用单位”但一旦把时间粒度变成15分钟很容易忘了算dt直接把电量算错。建议在数据预处理阶段就把所有值换算成“以15分钟为基准”的单位再把换算因子注释在代码旁边。项目里所有的时间相关计算我都显式乘以dt即使dt1也保留这个因子目的就是为了防止以后改时间粒度时出bug。还有一个小坑是电价表的时间和决策变量的时段索引错位。比如电价表从0点到24点而决策变量从第1个时段开始如果电价表的第一行对应的是0点到0点15分那么索引关系是price_buy(t)对应第t个时段千万别搞成从0开始索引Matlab数组从1开始很多Python转过来的人会在这方面栽跟头。5.4 从“能跑”到“能用”的几点心得第一一定要把可视化做在前面。没有图光看数字很难判断调度方案合不合理。我在项目里画了三张必备图功率堆叠图充电、放电、基础负荷和购电功率、SOC曲线图、变压器利用率图。这三张图一旦画出来任何模型错误都会非常直观地暴露出来。第二参数不要一次性全上。先把最简单版本跑通只有充电、没有放电只有功率平衡约束、没有SOC约束然后再逐步增加功能。每加一块就重新验证一次结果这样出问题时至少能确定是新增模块导致的。第三对调度结果要保留怀疑态度。优化器给出的解在数学上可行但工程上不一定好用。比如某些解可能让某辆车在一个小时内反反复复充放充放数学上成本最优实际上电池寿命和用户体验都会崩溃。我最终的方案是在目标函数里加了一个“状态切换惩罚”——如果上一时段和此时段的充放电状态不一致就加一个很小的固定惩罚。这一项几乎不改变总成本但能极大减少无意义的频繁切换实测效果好得惊人。6. 这套方案还能怎么扩展做完了全局加局部的两级调度后续自然有几个扩展方向。最直接的是把光伏和储能纳入进来现在园区停车场上方几乎必装光伏板分布式储能的削峰填谷作用跟电动车调度有很强的互补性模型上只是在功率平衡约束里再多加两个决策变量的问题。另一个方向是引入充电需求的不确定性预测——比如用马尔可夫链或神经网络预测车辆到达时间和初始SOC并把预测误差直接写进局部调度的鲁棒约束窗口里。还有一个比较前沿的玩法是考虑车网互动V2G参与电网辅助服务市场那就需要在调度里同时优化能量收益和容量收益两个时间尺度滚动的局部调度已经能承载这个概念。对多数读者而言从“全局定计划、局部做修正”这套两级架构起步是最稳妥也最容易出成果的路径。项目完整代码的骨架和关键函数都在前面的章节里了建议拿到手以后先改数据、跑通仿真、再调参数最后再考虑算法扩展。毕竟调度的本质不是算法炫技而是把约束管好、把目标算清、把结果画明白。能稳定跑出可行且平滑的调度方案比在论文里堆叠一个新算法重要得多。
网站建设高端定制企业官网