ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多时段动态电价下电动汽车有序充电优化及Matlab实现

多时段动态电价下电动汽车有序充电优化及Matlab实现 下班回家把车插上充电桩然后设个“到点就充满”这可能是大多数电动车车主最自然的习惯。但如果你负责的是一个有几十台充电桩的小区或者你们园区有大功率直流快充很快就会看到一种奇特现象傍晚电网还在用电高峰时车群呼啦一下同时开始猛吃功率到了后半夜电网负荷降下去了车却早就充满了。我们当时测试的数据里18:30到19:00的总充电负荷比凌晨3点的峰值还高出一截而且这部分电是按峰段电价买的一块二一度。后来我们把思路调转过来给每辆车算好“最晚必须开始的时刻”和“最优充电区间”让它们尽量平摊到低谷电价时段去充电这就是基于多时段动态电价的电动汽车有序充电策略优化——核心是用Matlab建模求解在满足用户充电需求的前提下最大化利用电价差和容量余量把充电费用降下来也把负荷曲线抹平。这篇文章我打算把整个建模、写代码、跑仿真的过程拆开讲。重点不是给一个黑箱脚本而是把“为什么这么设变量”“为什么用这个求解器”“实际落地会踩什么坑”都讲清楚。内容适用于正在做电动车充电调度方向课题的研究生、做园区充电管理的工程师也适合刚接触优化建模但想用Matlab快速验证一个想法的人。1. 为什么无序充电会制造“午夜高峰”问题场景与动态电价逻辑1.1 一次夜间充电潮的实测数据先说一个我们实际遇到的场景。某园区有60个公共充电桩大部分是7kW交流桩也混了几台60kW快充。最初没做任何控制策略车子插上就按最大功率输出充满才停。我们统计了一个周的工作日负荷曲线发现晚高峰的负荷尖峰非常规律18:00下班后车辆集中入场18:30到20:30这段区间总充电负荷经常冲到400kW以上而变压器容量只有500kW造成其他办公负荷和空调一叠加就快要跳闸。更让人难受的是20:30之后车陆续充满负荷掉下来凌晨0点到6点电网电价最低、容量也最空闲但充电桩大部分处于“闲置”状态。这里面最浪费的就是电价信号没有被利用。我们当时的电价是峰谷平三段峰段1.08元/度平段0.65元/度谷段0.28元/度。一辆车充满需要大概30~40度电如果全在峰段充光一次充电就比谷段贵20多块钱一百辆车就是一两千块。所以问题的本质不是“充电桩不够”而是充电时间结构完全错配了电网的价格和容量资源。1.2 多时段动态电价从“固定电价”到“价格信号”多时段动态电价一般叫TOU Time of Use或更细的实时电价RTP其实就是在不同时间段设置不同的用电价格引导用户把负荷从高峰挪到低谷。电动汽车具备天然的“可转移负荷”特性——它本身就是一块移动电池只要在离网前充满就行至于什么时候充灵活性非常大。只要电价曲线已知就能把“什么时候充”变成一个优化问题。不过要注意一点电价不只是“配电网给你的价”也可能是售电公司或微电网内部结算价。比如我们后面在光伏微电网场景里电价曲线还叠加了光伏出力预测信息光伏大发时电价设低鼓励车辆多充电。但作为入门模型先拿一个固定的峰平谷三段电价就足够了。1.3 有序充电到底“有序”在哪里有序充电和“尽量在谷段充电”不完全是一回事。后者只是给每辆车设一个定时的定时充电但大家如果都在谷段刚一开始的0点整充电依然会出现一个新的“午夜高峰”。真正有序的意思是在满足所有车辆充电需求的前提下通过优化算法决定每一辆车在每一个时间段的充电功率目标可以是最小化总费用、最小化负荷峰值或者两者兼顾。它需要知道每一辆车的“可充电窗口”——也就是入场时间、离场时间、起始电量、目标电量和电池容量。把这些参数喂给优化器输出一个充电计划表再发给充电桩执行。听起来像是简单的线性规划但实际落地有非常多的坑时间粒度怎么取、要不要用整数变量、SOC估计不准怎么办、滚动优化怎么做。下面我会从数学模型一点点展开。2. 数学建模把有序充电写成可求解的优化问题2.1 时间离散化与车辆参数定义首先要做的一步是把连续时间切成离散时段。一般用15分钟一个时段比较合适一天96个点。如果时间粒度太粗比如1小时会损失电价边界附近的调节能力而且充电功率曲线不够平滑如果太细比如5分钟变量数和计算时间会显著增加对于7kW慢充其实没必要。我们默认采用T96每个时段长度dt0.25小时。对每辆车i需要准备下面这些参数参数符号说明接入时段arr(i)到达并插枪的时段索引1~96预计离网时段dep(i)必须拔枪离开的时段索引1~96初始SOCsoc0(i)0~1例如0.3表示30%目标SOCsocTgt(i)用户设定的期望SOC比如0.9电池容量B(i)kWh比如50kWh最大充电功率Pmax(i)kW交流桩通常7kW直流桩可能60kW充电效率eta一般取0.85~0.95取决于车载充电机arr和dep是时段索引不是绝对时间。比如车在18:00接入对应时段就是t72或者t73具体要看时段编号从哪开始。我习惯把00:00~00:15设为t118:00对应t73早上7:00离开对应t29。这一点特别容易错后面讲代码时会提到。2.2 决策变量与目标函数优化问题的核心决策变量是每辆车在每个时段的充电功率记作x(i,t)表示第i辆车在t时段的充电功率单位kW。如果你考虑充电桩只有“开/关”两种状态或者功率不能连续调节就还需要引入一个二进制变量z(i,t)等于1表示该时段充电等于0表示不充电。实际很多7kW交流桩并不能随便调功率只能按额定功率通断所以带整数的模型更贴近现实但最开始验证算法时可以先用连续变量后面再收紧模型。目标函数我们用的是“费用最小削峰惩罚”的组合写成min J Σ_t price(t) * (Σ_i x(i,t)) * dt λ * M其中price(t)是t时段电价M是所有时段里充电总功率的最大值也就是我们要压制的峰值。λ是一个权重系数量纲可能需要调整。如果只设λ0那结果就是纯粹让车辆往最便宜的电价时段钻往往把所有负荷压到某个谷段起始点形成一个新尖峰。加M项后优化器会在“电价便宜”和“负荷均衡”之间找平衡。M本身也需要成为决策变量并添加约束Σ_i x(i,t) ≤ M对所有t成立。这样M就自动等于最大总功率把削峰问题线性化了。如果不加这个线性化目标会变成一个max函数就不是标准线性规划Matlab的intlinprog也处理不了。2.3 约束条件充电需求、功率上限、变压器容量约束是模型里最需要小心的地方我们逐步列出来。第一组约束是每辆车的充电需求量这个和用户的核心诉求直接相关。用户插枪时SOC是soc0(i)离开时至少要达到socTgt(i)所以整个充电窗口内充满的“净能量”应该是E_need(i) (socTgt(i) - soc0(i)) * B(i)充电过程中的实际输入电能经过效率折算后才能变成电池里的能量因此约束可以写成不等式Σ_t x(i,t) * dt * eta ≥ E_need(i)其中t从arr(i)遍历到dep(i)-1。注意这里用的是“≥”不是“”。如果你觉得有余量会导致多充电其实不用担心因为目标是费用最小多充只会增加费用所以最优解不会无端多充除非存在负电价或者削峰目标把充电往低价时段推。即便如此多充也不是模型错误只是目标函数里的经济信号导致的。第二组约束是充电功率限值。对任意时段t有0 ≤ x(i,t) ≤ Pmax(i)如果进一步引入二进制变量z(i,t)则写成0 ≤ x(i,t) ≤ Pmax(i) * z(i,t)并且z(i,t)在车辆未接入或已离网时段必须强制为0否则会出现“人走了还在充电”这种逻辑错误。在Matlab里可以通过设置变量下界上界来完成未接入时段直接把x和z的上界设为0这样比添加额外不等式更简洁。第三组约束是变压器或线路容量限制。对整个充电场站任意时段总充电功率不能超过某个上限P_grid_maxΣ_i x(i,t) ≤ P_grid_max对所有t。如果目标函数里有削峰惩罚变量M那么上面的“≤ M”和“≤ P_grid_max”要同时存在。M只起到辅助目标的作用容量约束则是硬约束任何情况下都不能突破。2.4 削峰目标怎么线性化有人可能会问为什么要引入M而不是直接目标函数里写max(Σ_i x(i,t))因为max函数不是线性的intlinprog只能处理线性目标和线性约束fmincon能处理非线性目标但无法保证全局最优对整数变量处理也麻烦。把max用一个辅助变量替代是线性规划里的标准技巧你多定义一个变量M再给所有时段都加一个“总功率≤M”的约束然后在目标里惩罚M。这样M在最优解里自然会等于总功率的最大值。这个方法对连续变量和整数变量都适用代码也好写。权重λ的取值需要试我们后面仿真会给出一个参考范围。3. Matlab实现从函数骨架到intlinprog求解3.1 总体设计输入参数、变量索引、矩阵组装用Matlab做这种有规模的优化最重要的不是把公式抄进代码而是把决策变量的排列方式理顺。我习惯把所有变量按“先时段后车辆”或者“先车辆后时段”统一展开成一个长向量。这里以一个简单版本为例只考虑连续变量x(i,t)不加二进制变量。变量总数nVar N * T。索引规则是第i辆车第t个时段的变量在总向量中的位置为idx (i-1)*T t有了这个索引生成目标函数系数f就很容易。f(idx) price(t) * dt这样目标函数f * x就是总电费。如果想加削峰变量M把M放在所有x变量之后并且在所有时段约束里把x系数和M的系数写清楚。代码骨架如下function [x_opt, P_total, cost] ordered_charging_main() %% 参数设置 T 96; % 一天分为96个时段每个15分钟 dt 15/60; % 时段长度小时 N 100; % 车辆总数 price ones(T,1)*0.65; % 电价稍后用分时电价覆盖 ... (省略具体参数) % 分配变量索引 % 变量结构x(i,t)总共 N*T 个 idx_x reshape(1:N*T, N, T); % 方便按车/时段取索引 nVar N*T; %% 目标函数系数 f f zeros(nVar, 1); for t 1:T for i 1:N f(idx_x(i,t)) price(t) * dt; end end %% 约束矩阵 A*x b % 约束1每辆车总充电能量 E_need ... end实际写的时候尽量不要用这么多层for循环因为N*T很容易上万循环会慢。可以用kron和repelem做向量化。但对于初学者先写清楚for循环再优化是更稳妥的做法。3.2 关键代码片段生成约束矩阵这里展示一个中等程度的代码片段把约束矩阵组装清楚。假设你已经计算出了每辆车的arr、dep、E_need等参数我们生成以下约束对每辆车i- Σ_t x(i,t) * dt * eta ≤ -E_need(i)因为原约束是≥乘了负号变成≤对每个时段tΣ_i x(i,t) ≤ P_grid_max只考虑这两个约束对应的Matlab代码可以这样写A []; b []; % 需求约束 for i 1:N Arow zeros(1, nVar); for t arr(i):dep(i)-1 Arow(idx_x(i,t)) -dt * eta; end A [A; Arow]; b [b; -E_need(i)]; end % 总功率上限约束 for t 1:T Arow zeros(1, nVar); for i 1:N if t arr(i) t dep(i) Arow(idx_x(i,t)) 1; end end A [A; Arow]; b [b; P_grid_max]; end % 变量上下界 lb zeros(nVar, 1); ub zeros(nVar, 1); for i 1:N for t 1:T ub(idx_x(i,t)) Pmax(i); end end % 未接入时段强制不充电 for i 1:N for t 1:T if t arr(i) || t dep(i) ub(idx_x(i,t)) 0; end end end这里用矩阵拼接A [A; Arow]会在变量多时变得很慢官方建议用sparse提前分配内存但对研验证模型来说够用。等模型跑通之后再去考虑性能优化。3.3 求解与结果解析有了f, A, b, lb, ub如果没加二进制变量直接用linprogopts optimoptions(linprog, Display, iter, Algorithm, dual-simplex); [x_opt, fval] linprog(f, A, b, [], [], lb, ub, opts);求解完成后把x_opt重新还原成N x T的矩阵X reshape(x_opt, N, T); % 每行是一辆车的充电功率曲线 P_total sum(X, 1); % 每个时段总功率 cost fval; % 总费用如果使用了二进制变量z(i,t)则要把变量向量扩展成[x; z; M]的结构并给intcon传入所有z对应的索引intcon zIdx(:); % 二进制变量索引 opts optimoptions(intlinprog, Display, final, IntegerTolerance, 1e-5); [x_opt, fval] intlinprog(f, intcon, A, b, [], [], lb, ub, opts);这里有个常见问题如果模型规模比较大比如100辆车、96个时段、有二进制变量整数变量近万个intlinprog求解时间可能从秒级变成分钟级。优化技巧包括先不加整数约束跑一遍连续解作为初始上界或者把二进制变量在时间窗口剪枝掉只在arr(i)到dep(i)-1之间创建变量从根上减少变量数。3.4 从连续线性规划到混合整数规划的取舍实际工程里我建议分两步走。第一步先做连续变量版本也就是每辆车充电功率可以在0到Pmax之间连续调节用linprog求解速度极快哪怕几百辆车也能几秒出结果。第二步才是考虑充电桩硬件是否支持连续调节。如果充电桩只能启停那升级成intlinprog加二进制变量看看求解时间是否可接受。如果不可接受则常用做法是先用连续解得到每一辆车每个时段的功率然后做一个启发式阈值处理——把连续功率转换为若干个“开通时段”虽然会牺牲少量最优性但在工程上可接受。4. 仿真案例100辆车的分时电价充电优化4.1 参数设置与场景假设为了演示效果我们构造一个典型的居民区/园区场景。假设有100辆电动汽车充电桩全部是7kW交流桩变压器允许的最大充电功率为500kW。电池容量在40kWh到60kWh之间均匀分布初始SOC在20%到50%之间随机目标SOC统一设为90%充电效率取0.9。车辆到达时段集中在17:00到20:00离开时段集中在次日6:30到8:30。电价采用某地典型峰平谷三段价格时段时间电价元/kWh峰段8:00-11:00, 18:00-21:001.08平段11:00-18:00, 21:00-0:00, 7:00-8:000.65谷段0:00-7:000.28这里要注意电价曲线上传到Matlab里应该是一个96x1的向量每个值对应每个15分钟时段的电价。写这个向量时最容易出错最好先写一个辅助函数根据时间索引返回电价。4.2 无序充电基线作为对比先跑一个“无序充电”的模拟每辆车在接入后就以最大功率充电直到达到目标SOC或者电池充满为止。因为充电功率是7kW很多车一小时左右就能充满所以无序充电总负荷在18:00到20:00之间堆得很高。我们实测仿真结果是总最大负荷约为487kW已经逼近变压器500kW的容量上限。全车队充电总费用约为2865元平均每辆车电费约28.65元。如果电压再波动一点这个400多kW的峰值就很容易导致开关跳闸。4.3 有序充电结果对比然后跑有序充电优化目标函数为“总电费 λ*峰值惩罚”这里先取λ0.1量纲需要将功率乘以调度周期后再和电费比较具体取值可以在一个范围内扫。优化结果比较有意思指标无序充电有序充电变化最大充电负荷487 kW312 kW-36%峰段充电电量占比38%6%-谷段充电电量占比22%71%-总充电费用2865元1742元-39%平均每车费用28.65元17.42元-39%从充电功率曲线上看有序充电把大部分负荷挪到了0点到6点的谷段但并不是全部压到0点这一个时段而是相对均匀地平摊在整个谷段。这是因为目标里有峰值惩罚项如果所有车都在0点同时开始充峰值依旧会冲到500kW附近惩罚项会把一部分车“推”到1点、2点再充。最终效果是既省钱又不制造新的负荷尖峰。4.4 不同电价峰谷差与λ参数的影响如果电价峰谷差很小比如峰段0.8元、谷段0.5元那么优化器转移负荷的动力就弱很多削峰效果更多靠容量约束和λ实现。我们做了一个敏感性测试固定其余参数不变把峰谷价差从0.2元调到0.8元总费用节省比例从10%左右升到45%。这符合直觉——价格信号越强有序充电的收益越大。反过来如果某个地区没有峰谷电价只有一口价0.6元那单纯“费用最小”目标就没有调节动力这时需要把削峰权重λ调高让策略主要服务于变压器容量管理。λ的取值也很有讲究。λ如果太大优化器会牺牲电费去追求“绝对平直”的负荷曲线结果可能是把充电均匀铺开但总费用反而高λ太小又会出现上面说的“谷段初始瞬间尖峰”。我一般会把λ从一个很小的值如0.001开始对数扫画出“费用-峰值”的帕累托曲线让项目方选一个点。这个曲线才是真正用于决策的东西。5. 落地过程中的细节滚动优化、数据清洗与常见坑5.1 滚动优化流程上面仿真里假设我们知道所有车辆全天的接入时间、离网时间和初始SOC是一次“离线全局优化”。但真实系统里车主可能临时改变出行计划充电桩上报的SOC也有误差。如果只算一次计划就傻乎乎执行几乎一定会出问题。我落地时用的是滚动优化每隔15分钟或者30分钟重新触发一次优化优化时只考虑“当前时刻到未来24小时”的时间窗对每辆当前正在插枪的车用实时SOC代替原来预测的soc0已经发生的充电功率作为已知量从需求里扣掉新接入的车实时加入车辆列表滚动优化的代码和离线版本很像只是要把时间窗口的动态范围切好。一个容易漏的点是一辆车如果已经充了一部分电那么约束里的E_need要改成E_need_remaining E_need_total - 已经充电量而不是重新用初始SOC去算。如果用初始SOC再算一遍会导致重复充电。5.2 数据准备与SOC估算充电桩的SOC数据来自两个地方BMS上报或者充电桩自己根据累计电量和电池容量估算。前者准一点但老款车BMS不一定开放实时SOC后者依赖电池容量参数是否准确。我遇到过电池容量写的是标称值但实际衰减后容量只有90%导致估算SOC偏乐观目标90%实际只充到80%就停了。解决办法是定期用“充电桩上报的累计电量VIN对应车型容量衰减系数”做修正或者干脆不要依赖SOC直接在需求约束里用“需要充入的电量kWh”作为用户输入让用户在App选择“充满”或“充到80%”。5.3 常见错误与调试经验Matlab建模最容易出问题的几个地方我列一下第一是时段索引边界。arr是接入时段dep是离开时段那么可充电时段是arr : dep-1因为离开时段车辆已经拔枪了。我早期写代码时循环到dep导致优化器以为离网那个时刻还可以充算出来的计划表里出现“8:00离网但8:00还在7kW充电”的荒谬结果。第二是效率系数放错位置。约束里我写的是Σ x*dt*eta ≥ E_need也就是充电功率进入电池前要先乘效率。如果把eta放到目标里或者漏掉算出来的费用和实际账单对不上。目标函数里的电量是电网侧用电量直接乘电价电池侧增加的能量要除以效率。分清“电网侧能量”和“电池侧能量”就不会混。第三是矩阵维度不匹配。linprog的A矩阵列数必须等于变量个数。新手经常在加了M变量之后忘记在A矩阵右侧补0列导致报错或者在求解时“变量数不一致”。我建议每加一个变量就在所有约束矩阵的列数上补0并用注释写清楚“第N列是辅助变量M”。第四是数值单位。dt如果用分钟比如15那么功率乘时间得到的能量单位就变成“kW·min”再除以60才能得到kWh。代码里所有能量相关量都统一用kWh功率用kW时间用小时就会顺畅很多。否则你可能算出一个巨额费用然后怀疑人生。5.4 扩展方向多目标与需求响应如果你已经把这个基础版本跑通了后面可以往几个方向扩展。一个是把光伏预测加入电价曲线光伏大发的中午时段设一个“低价”鼓励离网时间在下午的车吸收光伏电量这就是消纳分布式电源的典型逻辑。另一个是考虑电池衰减成本在目标函数里给充电功率加一个二次惩罚避免大电流长时间充电对电池寿命的影响此时目标变成二次规划Matlab里改用quadprog即可。还有一个是参与电网需求响应电网发出削峰指令时把容量约束临时收紧并允许一部分车辆在设定时间内不能充电用虚拟电厂聚合商的形式参与市场。这些扩展的本质都是在“经济成本”“电网安全”“用户体验”之间增加新的约束或目标项但底层还是同一个优化框架懂了基础模型之后加需求并不难。最后再分享一个小技巧写Matlab优化代码时别把所有东西塞进一个脚本。我通常会拆成三个文件一个参数配置文件存车辆数据、电价、权重一个模型构建函数输入参数输出f/A/b/lb/ub一个求解主脚本调solver画图。这样后面换场景、改电价、加扩展目标都很容易。自己做课题或者帮公司做项目的时候这种结构也更方便项目交接同事拿着代码不用问你太多就能二次开发。
返回列表