ARTICLE DETAIL

资讯详情

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

多时段动态电价下的电动汽车有序充电策略与Matlab实现

多时段动态电价下的电动汽车有序充电策略与Matlab实现 最近在Matlab里做了一版基于多时段动态电价的电动汽车有序充电策略优化把思路、模型和可运行的代码一起整理出来。起因是帮朋友评估一个住宅小区的充电桩规划发现下班回家即插即充的模式太典型了电动车主18:00左右到家正好撞上晚高峰充电功率又把小区配变顶到红线电费账单也难看。后来引入多时段动态电价作为调度信号把充电负荷平移进谷段问题才真正变成“什么时候充、充多少”的优化问题而不是简单一句“晚点再充”。这个方向适合正在做电动汽车有序充电、需求侧响应、小区充电桩容量评估的读者。Matlab代码可以直接跑参数也给了默认值你拿到手里先复现再根据自己的电价和车辆参数去改。下面我会把为什么要这么建模、约束怎么列、linprog和quadprog怎么用、以及实测中容易踩的坑一起讲清楚。1. 有序充电到底在优化什么从无序到有约束的自主决策1.1 无序充电为什么又贵又挤无序充电的核心特征是车主到家时间决定充电开始时间充电功率固定直到充满或者第二天出行才停止。看起来没什么问题但把大量车辆放在同一个场景里看问题就出来了。傍晚18:00到21:00本来就是生活用电高峰空调、热水器、电磁炉都在工作。电动车这时候接入相当于在峰值负荷上又叠了一层大功率负荷。一台7kW慢充桩的电流接近32A单相一个单元十几台车同时充配变很容易过载。电网侧的矛盾先不说车主自己的电费账单也不好看——因为多时段动态电价下傍晚恰恰是一天中最贵的时段。无序充电还有一个隐性成本它把“充电负荷”当成一个不可调度的刚性负荷导致配网容量必须按“最坏情况”去设计。你原本只需要为夜间基础负荷配的变压器容量因为无序充电可能要多扩容30%到50%而扩容成本最终会分摊到每个用户头上。1.2 动态电价是“用价格换时间”的调度信号多时段动态电价的本质是把不同时刻的供需紧张程度转化成价格差异。凌晨负荷低、发电出力宽裕电价就低傍晚负荷尖峰、备用紧张电价就高。电动车的充电负荷天然具备“可平移”属性——只要在出发前完成充电具体哪一小时充、功率多大是可以灵活调整的。所以在优化模型里电价不是简单的一个成本系数而是整个调度逻辑的信号源。价格曲线越陡优化出来的充电策略就越倾向于把负荷挤到谷段价格曲线平缓优化结果则更接近均匀充电。这也是为什么我建议你先别急着调算法先把电价序列和数据格式搞清楚后面所有结果都依赖它。这里说的动态电价可以理解为“分时电价”的扩展版分时电价通常是固定的峰平谷三档而动态电价可能是日前预测值、实时激励系数甚至是市场出清价格的近似。模型处理起来没什么区别都是给每个时段一个price(t)。我会用一组典型的分时价格做演示你换成96点日前电价曲线也一样能跑。1.3 有序充电能控制什么、不能控制什么建模之前最忌惮把问题扩大化。有序充电策略能控制的变量是有限的每个时段的充电功率P_t可以是连续值也可以是离散档位充电开始时间和结束时间目标SOC和是否需要参与反向馈电V2G扩展是否考虑变压器容量约束限制同时充电的车数或总功率。不能控制的是电池的初始SOC、出行时间、基础负荷曲线、电价曲线。这些在模型里一律当作已知输入而不是决策变量。很多初学者容易把模型写成“既优化充电又优化电价”甚至“优化出行时间”这会把问题变成非线性混合整数规划求解难度和工程落地难度完全不同。我先讲清楚边界下面所有约束都不越界。2. 模型设计目标函数、约束条件与参数约定2.1 目标函数的两层设计在进行Matlab实现前先把数学模型写清楚。我用的决策变量是一天N个时段的充电功率x长度为N的列向量单位kW。第一层目标是最小化充电费用min sum(price(t) * x(t) * dt)dt是每个时段的时长如果按小时分段就为1按15分钟分段就为0.25。这一层目标直接对应用户最关心的“省钱”。第二层目标是平抑负荷波动。单纯追求电费最低可能出现所有电动车都在凌晨0点一起开始充的情况这在单辆车模型上看不出来但放到100辆车的小区里就是新的负荷尖峰。所以我加入一个二次惩罚项min sum(price(t) * x(t) * dt) lambda * sum((baseLoad(t) x(t) - mean(baseLoad x))^2 * dt)baseLoad(t)是基础负荷曲线lambda是权重系数。lambda0就是纯费用最优lambda越大充电负荷越倾向于避开基础负荷的波峰把总负荷曲线拉平。在Matlab里第一层目标用linprog就能解因为目标函数和约束都是线性的。加上第二层目标后目标函数变成了二次型要用quadprog。我会分别给出两版代码方便你对比。2.2 约束条件电池、功率和时间窗约束条件看起来多拆开其实就四类。功率上下限约束0 x(t) Pmax % Pmax为充电桩最大充电功率比如7kW如果某个时段不允许充电直接把该时段ub设成0即可比如出行前1小时禁止充电。SOC递推关系SOC(t1) SOC(t) x(t) * eta * dt / Capeta是充电效率取值0.9到0.95我默认用0.92Cap是电池容量单位kWh。注意这里功率单位是kW、时间单位是小时乘出来就是kWh。SOC上下限约束SOCmin SOC(t) SOCmax实际程序里我不会每个时段都写SOC递推等式因为如果x是变量、SOC也是变量约束数量会翻倍。更简洁的做法是只写“前缀和”形式的不等式任意时刻累计充入电量不能超过SOCmax - SOC0对应的电量也不能低于保障出行的下限。终端电量约束SOC(N) SOC_target也就是出发时电量要达到设定目标。这个约束是刚性的达不到目标说明充电时间或功率不够需要调整参数。2.3 示例参数24时段还是96时段我选择用24时段做演示每个时段1小时直观、好调试画图也容易看。实际项目里建议用96时段每段15分钟能更好描述分时电价拐点。两者在代码上的区别只是N和dt变了约束矩阵的结构完全一样。我这组仿真参数如下参数值说明电池容量 Cap60 kWh主流纯电车型范围初始SOC0.25下班到家剩余电量目标SOC0.95第二天出发期望电量最大充电功率7 kW单相慢充桩典型值充电效率 eta0.92含AC/DC损耗时段数 N24每段1小时电价谷段0.40 元/kWh23:00-6:00电价平段0.70 元/kWh6:00-8:00, 10:00-18:00, 21:00-23:00电价峰段1.20 元/kWh8:00-10:00, 18:00-21:00所需充入电量 (0.95-0.25)*60 / 0.92 45.652 kWh以7kW连续充电需要6.52小时。而谷段只有6小时所以最优解必然会有少量充电落在平段这种“不够完美”的不对称反而更能看出优化算法的分配能力。3. Matlab实现从电价数组到调度曲线3.1 先把电价和基础负荷做成数组Matlab里做优化第一件事不是调优化器而是把所有输入数据整理成向量。我建议用脚本开头一次性定义参数后面所有计算都引用这些变量改参数只改一处。%% 基本参数 N 24; % 24个时段 dt 1; % 每段1小时 Cap 60; % 电池容量 kWh soc0 0.25; % 初始SOC socTar 0.95; % 目标SOC socMax 0.95; % 允许最大SOC Pmax 7; % 充电功率上限 kW eta 0.92; % 充电效率 %% 构造24小时电价数组注意长度必须等于N % 时段划分0-5谷6-7平8-9峰10-17平18-20峰21-22平23谷 price [0.4*ones(1,6), 0.7*ones(1,2), 1.2*ones(1,2), ... 0.7*ones(1,8), 1.2*ones(1,3), 0.7*ones(1,2), 0.4*ones(1,1)]; price price(:); % 转成列向量 %% 基础负荷曲线kW baseLoad [2.5, 2.3, 2.2, 2.1, 2.0, 2.0, 2.2, 3.0, 3.8, 4.2, ... 4.0, 3.5, 3.6, 3.8, 4.0, 4.2, 4.8, 6.0, 6.8, 6.5, ... 5.5, 4.5, 3.5, 2.8]; baseLoad baseLoad(:); %% 充电所需总电量 needEnergy (socTar - soc0) * Cap / eta;这段代码没有技术难点但price数组的长度很容易出错。我习惯写完以后立刻检查length(price)如果和N对不上后面所有约束矩阵都会报维度错误。3.2 版本A用linprog求最低电费充电策略先做纯费用最优版本。约束矩阵的思路是用前缀和表达“任意时刻累计充电量不能超过SOC上限允许值”用总和表达“最终电量必须达到目标”。%% 约束矩阵 % 决策变量 x 是N维列向量x(t)为第t时段充电功率 % 1) 任意时刻SOC socMax % soc0 chargeEff * sum_{i1}^t x(i) socMax % 对每个t分别写一行形成上三角矩阵 chargeEff eta * dt / Cap; A_ub1 triu(ones(N,N)) * chargeEff; b_ub1 (socMax - soc0) * ones(N,1); % 2) 最终SOC socTar % soc0 chargeEff * sum(x) socTar % 改写为 -chargeEff * sum(x) soc0 - socTar A_ub2 -chargeEff * ones(1,N); b_ub2 soc0 - socTar; A [A_ub1; A_ub2]; b [b_ub1; b_ub2]; % 3) 功率上下限 lb zeros(N,1); ub Pmax * ones(N,1); %% 线性规划求解 f price * dt; % 目标函数系数min f*x options optimoptions(linprog, Display, iter, Algorithm, dual-simplex); x_lin linprog(f, A, b, [], [], lb, ub, options); %% 后处理 SOC_lin soc0 cumsum(x_lin) * eta * dt / Cap; cost_lin price * x_lin * dt; peakDiff_lin max(baseLoad x_lin) - min(baseLoad x_lin);这里A_ub1每一行都是前缀和含义是“到达当前时段结束时累计充入的电量不许突破SOC余量”。由于充电功率非负SOC只会单调上升下限约束自动满足不需要额外写。跑出来的结果符合直觉linprog会把充电功率尽可能放到23:00到6:00的谷段但谷段总容量只有7*642kWh离需要的45.652kWh还差3.652kWh这部分会放到电价第二低的平段。注意它不是放到早晨6点而是可能放到21:00-22:00的平段因为还要受SOC上限约束晚充比早充更安全且价格一样的情况下优化器倾向选择更靠后的时段来避免触发SOC上限。这版代码解出来的电费大约在19.4元左右。如果采用无序策略18:00到家直接充费用在35.7元左右节省接近46%。3.3 版本B用quadprog实现费用与波动的折中只有费用目标是理想化的。实际项目里电网侧和运营商更关心负荷曲线是否平滑。加入二次惩罚项后需要用quadprog。先把波动项写成标准二次型。令中心化矩阵M eye(N) - ones(N,N)/N;那么总负荷baseLoad x的方差乘以N就等于(baseLoadx)*M*(baseLoadx)。展开后二次项系数矩阵是lambda*M线性项除了电价还要加上2*lambda*M*baseLoad。因为quadprog的模型是0.5*x*H*x f*x所以H 2*lambda*M。%% 多目标费用 lambda * 总负荷波动平方 lambda 1.0; M eye(N) - ones(N,N)/N; % 二次项矩阵 H 2 * lambda * M; % 线性项 电价 交叉项 f_q price * dt 2 * lambda * M * baseLoad; % 约束与版本A一致 x_quad quadprog(H, f_q, A, b, [], [], lb, ub, [], options); %% 后处理 SOC_quad soc0 cumsum(x_quad) * eta * dt / Cap; cost_quad price * x_quad * dt; peakDiff_quad max(baseLoad x_quad) - min(baseLoad x_quad);quadprog对H矩阵要求是对称半正定M是幂等对称阵半正定没问题。lambda默认取1.0你可以当成灵敏度开关去调。加了波动惩罚后充电功率不再完全集中在同一个谷段而是会往平段稍微摊开一些。代价是电费从19.4元上升到21元左右但总负荷峰谷差会明显变小。这体现的就是多目标优化的本质没有免费的全都要只有看你怎么权衡。3.4 两种策略的结果对比我建议把无序、纯费用最优、费用加波动最优三条曲线画在同一张图里。%% 对比绘图 t 0.5:1:23.5; % 时段中点用于横轴 figure(Color, w); subplot(2,1,1); stairs(0:24, [baseLoad; baseLoad(end)], k-, LineWidth, 1.2); hold on; stairs(0:24, [baseLoad x_lin; baseLoad(end) x_lin(end)], b--, LineWidth, 1.2); stairs(0:24, [baseLoad x_quad; baseLoad(end) x_quad(end)], r-., LineWidth, 1.2); legend(基础负荷, 费用最优总负荷, 费用平抑总负荷); xlabel(时刻 (h)); ylabel(功率 (kW)); grid on; subplot(2,1,2); plot(t, SOC_lin, b-o, LineWidth, 1.2); hold on; plot(t, SOC_quad, r-s, LineWidth, 1.2); yline(soc0, --); yline(socTar, --); xlabel(时刻 (h)); ylabel(SOC); grid on; title(SOC曲线对比);从我的算例看无序策略从18:00开始充SOC一路爬升到23:30左右才到目标纯费用最优则从23:00才开始明显爬坡清晨前充满加了lambda的版本会在21:00-22:00多充一点后半夜充电曲线更平缓。这个SOC曲线的形状就是策略的“指纹”一眼能看出算法在做什么。4. 调试时最容易出的问题与下一步扩展方向4.1 约束矩阵拼接变量顺序和维度不匹配是头号bugMatlab里跑优化报错90%出在约束矩阵上。linprog要求A的列数等于决策变量个数也就是N。如果你的price是行向量x_lin是列向量计算price*x时一时看不出问题但f如果是行向量也可能被自动转置掩盖掉。我的排查习惯是三步走先size(A)确认维度是(N1)*N再打印x_lin前几个值看是不是全0或全在边界上最后把约束手写一两条和矩阵对应行对比。还有一个隐蔽问题triu(ones(N,N))生成的是对角线及以上的前缀和矩阵表示第t行的非零元素是1:t。如果某天你想改成“只允许在指定窗口充电”正确做法是修改对应时段的ub而不是去改A矩阵。4.2 lambda怎么定别拍脑袋画一下权衡曲线lambda是费用和波动的折中系数。实测中我见过两种极端lambda设得太大充电功率被拉成一条水平线完全失去动态电价的意义lambda设得太小又跟纯费用最优没区别。建议做一次简单的灵敏度扫描lambdaList 0:0.1:3; costList zeros(size(lambdaList)); peakDiffList zeros(size(lambdaList)); for i 1:length(lambdaList) lambda lambdaList(i); H 2*lambda*M; f_q price*dt 2*lambda*M*baseLoad; x_q quadprog(H, f_q, A, b, [], [], lb, ub, [], options); costList(i) price*x_q*dt; peakDiffList(i) max(baseLoadx_q) - min(baseLoadx_q); end plot(costList, peakDiffList, o-);画出来通常是一条下凸曲线拐点附近就是比较合理的lambda。没有绝对正确答案但能看到你的“省钱”和“降峰谷差”边界在哪。实际操作里我还会打印每次求解是否收敛quadprog有时会因为数值问题提前退出表现为exitflag不为1这时候需要检查数据量级或调ConstraintTolerance。4.3 从单辆车扩展到聚合充电的几步走单辆车模型跑通后常见的方向是扩展到多车聚合。这时一个充电桩的Pmax约束要换成“充电桩群总功率约束”比如一个配变下允许的最大充电总功率每辆车的SOC递推和终端约束都各自独立可行解域会变成多组变量的笛卡尔积。如果加入V2G决策变量x_t就变成双向的负值表示放电功率下限从0改成-Pmax目标函数里电价项也变成“放电收益”。这时模型仍然可以保持线性只是约束条件里需要额外处理“不能同时充放”这样的逻辑约束可以引入二进制变量也可以用互补约束表示。这部分我还没有展开做但基础版本跑通以后扩展路径是清晰的。4.4 Matlab版本与求解器接口的兼容性最后提醒一个容易劝退新手的点不同Matlab版本的linprog和quadprog参数名有细微差异。老版本可能不支持dual-simplex算法名新版本则可能把MaxIterations改成了MaxIterations和ConstraintTolerance。不要背参数名直接用doc linprog查当版本签名。如果你用的是Matlab R2021a之后的版本optimoptions设置算法名的写法有时会被警告“忽略算法选项”这不影响结果但说明该算法已被自动选择。只要exitflag返回1就说明问题求解成功。就个人经验我还会在代码最后加一个断言assert(abs(SOC_quad(end) - socTar) 1e-6, 终端SOC未达到目标);别小看这一行它能帮你迅速区分“策略差异”和“约束根本没满足”。毕竟优化模型最尴尬的结局不是算得慢而是算完了才发现终端电量不够第二天车主开不走车。
返回列表