詳解)
很多剛開始做電動汽車調度的朋友都會把“基于V2G技術的電動汽車實時調度策略”理解成“讓每輛車算一個充放電功率”。真上手之后才發(fā)現(xiàn)V2G實時調度更像是在管一支隨時有人進出、電池狀態(tài)千差萬別的車隊同時還要和電網的負荷曲線、分時電價、變壓器容量綁在一起做博弈。最近我完整實現(xiàn)了一遍這個策略用的是Matlab從建模、選算法到寫代碼調試前前后后折騰了近兩周。如果你也在做電動汽車調度、微電網能量管理或者儲能優(yōu)化方向這篇筆記應該能幫你少走很多彎路。1. 項目整體思路與調度模型構建1.1 實時調度到底在調度什么V2GVehicle-to-Grid的核心價值是讓電動汽車在“從電網取電”和“向電網送電”兩個方向都能靈活響應。但真正落到調度問題上事情就具體了每輛車有自己的接入時間、離開時間、初始SOC、目標SOC和充放電功率上限配電臺區(qū)有一條時變的基礎負荷曲線和一個變壓器容量約束電網側還有分時電價。實時調度要做的就是在每個調度周期比如每15分鐘根據當前已經接入車輛的狀態(tài)和未來一段時間的預測數據決定接下來每小時甚至幾分鐘內每輛車的充放電功率。我用的模擬場景是某園區(qū)或小區(qū)充電站下面有20輛電動汽車調度間隔取15分鐘一天96個周期。車輛之間互不共享電池但共享配電變壓器容量。這種場景非常典型既不會太簡單到沒有說服力也不會因為規(guī)模太大導致Matlab跑不動。如果只做單輛車的充放電策略你會發(fā)現(xiàn)約束太少算法沒什么可調但直接做大幾十輛車、上千個節(jié)點又容易陷入調試地獄。20輛車是個很好的中間起點。1.2 目標函數與約束條件的數學表達調度目標不能拍腦袋定成“費用最低”。如果只優(yōu)化用戶充電費用群體會自發(fā)把充電需求全部擠進谷電時段結果峰谷差可能不減反增配電臺區(qū)在夜間又冒出一個新的負荷尖峰。所以我在目標函數里做了多目標權衡最小化所有車輛在預測窗口內的凈購電成本最小化配電臺區(qū)峰值負荷、平抑負荷波動對頻繁放電帶來的電池損耗加一個懲罰項。目標函數寫出來大致是[ \min \quad J \sum_{t \in T} \big[ \lambda_1 \cdot p_t \cdot \sum_i P_{net,i,t} \lambda_2 \cdot L_{peak} \lambda_3 \cdot \sum_i D_{i,t} \big] ]其中 (P_{net,i,t}) 表示車輛i在t時刻從電網取電的凈功率正值是充電負值是放電。(p_t) 是分時電價(L_{peak}) 是窗口內最大負荷(D_{i,t}) 是電池退化相關的放電深度懲罰。實際求解時(L_{peak}) 我會寫成線性化形式也就是給峰值負荷增加一個輔助變量再加不等式約束。約束條件分為四類首先是配電臺區(qū)功率平衡約束。某個時刻變壓器出口總功率等于基礎負荷加上所有車輛的凈充電功率這個總功率不能超過變壓器容量上限低于安全下限的情況也盡量避免。其次是單車功率約束。每輛車在同一個調度周期內不能既充電又放電充放電功率絕對值都不能超過車端變換器的額定功率。這個額定功率我按常見的7kW交流樁來設置電池容量取50kWh對應現(xiàn)在主流的長續(xù)航車型。再是電池SOC動態(tài)方程[ SOC_{i,t1} SOC_{i,t} \frac{P_{ch,i,t} \cdot \eta_c \cdot \Delta t}{E_i} - \frac{P_{dis,i,t} \cdot \Delta t}{E_i \cdot \eta_d} ]這里 (P_{ch}) 是充電功率(P_{dis}) 是放電功率(\eta_c) 和 (\eta_d) 分別是充放電效率我統(tǒng)一取0.95。SOC的范圍設定在0.2到0.9之間避免過充過放對電池的傷害。最后是離網SOC約束。每輛車離開的時候SOC至少要達到用戶設定的目標值但也不需要強制充滿。這個約束最容易導致求解失敗因為車輛接入時間短、初始SOC低、充電功率有限很可能在離開前充不到目標值。后面我會專門講怎么處理。1.3 為什么“實時”比“日前”更適合V2G很多論文先做“日前調度”也就是基于預測曲線一次性算出24小時的充放電計劃再讓車輛執(zhí)行。這個方法在歷史負荷平穩(wěn)、車輛行蹤規(guī)律時效果還行但一旦遇到車輛接入時間隨機、臨時改計劃、或者天氣變化導致基礎負荷波動日前計劃就失效了。實時調度采用“滾動窗口”的思路每個調度周期只求解未來4小時或者1小時的控制指令但只執(zhí)行第一個周期的功率到下一個15分鐘再刷新狀態(tài)、重新求解。這也是模型預測控制MPC的經典框架。好處是對預測誤差的容忍度高車輛隨機接入也能隨時納入優(yōu)化不需要重跑全天計劃。代價是求解頻率變高算法必須足夠快。我在Matlab里用線性規(guī)劃實現(xiàn)滾動窗口單次求解時間基本在幾十毫秒量級完全滿足15分鐘一個周期的實時性要求。2. 核心算法選型與Matlab實現(xiàn)要點2.1 幾種常見調度算法怎么選V2G調度算法從數學上看是一個約束優(yōu)化問題可選的方法非常多。我自己做過對比把結論整理成表格算法類型優(yōu)點缺點適合場景線性規(guī)劃LP求解快、全局最優(yōu)、模型直觀目標函數和約束必須是線性的功率變量連續(xù)、費用和負荷優(yōu)化混合整數線性規(guī)劃MILP能建模0/1開關、啟停狀態(tài)求解速度隨整數變量數明顯下降需要嚴格禁止同時充放電、多檔位控制動態(tài)規(guī)劃理論上能得到全局最優(yōu)狀態(tài)空間爆炸車輛多時基本不可行單車或少車輛調度遺傳算法/粒子群適應強非線性目標參數敏感、每次結果可能不一致、實時性差離線方案設計、參數標定MPC滾動優(yōu)化能處理不確定性、滾動修正需要模型和求解器配合實時調度、復雜系統(tǒng)控制我最終選了“滾動時域LP”作為主方案。原因有兩個一是V2G調度模型在15分鐘粒度下SOC更新、功率約束都可以寫成線性關系不需要非線性求解二是LP能夠保證每次求解的穩(wěn)定性和速度方便我做重復實驗對比不同參數。如果你想把“同周期不能同時充放電”這個邏輯做實可以在LP基礎上加0/1變量升級成MILP但需要接受更大的計算開銷。我的經驗是在正常分時電價且目標函數有費用懲罰的前提下LP最優(yōu)解天然不會同時充放電顯式加上“功率之和小于上限”的約束已經足夠了。2.2 滾動時域LP的完整流程實時調度的主循環(huán)我用偽代碼表示會更清楚初始化時間戳、電價曲線、基礎負荷曲線、車輛參數。對每個調度周期k 1:T只保留當前仍接入、且還沒到離開時間的車輛更新每輛車的最新SOC讀取未來H個周期的電價預測和基礎負荷預測構建LP模型定義決策變量、目標函數、約束調用solve得到最優(yōu)功率序列只取第一個周期的控制指令更新車輛狀態(tài)存下結果進入下一輪。這里有個容易被忽略的細節(jié)每個調度周期內離開時間早于預測窗口結束的車輛在離開后的時段就不應該再出現(xiàn)在約束里。實現(xiàn)時我會生成一個“可用車輛-時段”的掩碼矩陣所有功率變量都乘以這個掩碼。否則車輛已經離開SOC約束還在強制它參與調度這既不合理也會造成不可行。2.3 Matlab代碼的基本骨架與變量設計最開始我把所有代碼堆在一個大腳本里結果改一個參數要翻半天后來重構成了三個模塊參數定義、模型構建、滾動循環(huán)。車輛參數我統(tǒng)一用struct數組保存每個元素包含電池容量、最大功率、接入/離開時間、初始SOC、SOC上下限、目標SOC和充放電效率。核心的滾動循環(huán)代碼長這樣for k 1:totalPeriods horizon min(H, totalPeriods - k 1); active (veh.startTime k) (veh.leaveTime k); prob optimproblem(ObjectiveSense, minimize); Pch optimvar(Pch, nEV, horizon, LowerBound, 0); Pdis optimvar(Pdis, nEV, horizon, LowerBound, 0); % 目標函數購電成本 峰負荷懲罰 放電懲罰 cost 0; for t 1:horizon netLoad baseLoad(kt-1) sum(Pch(:,t)) - sum(Pdis(:,t)); cost cost price(kt-1) * sum(Pch(:,t) - Pdis(:,t)) ... penalty * netLoad; end prob.Objective cost; % 約束功率上限、變壓器容量、SOC更新等 prob.Constraints.powerUpper Pch Pdis veh.maxPower; prob.Constraints.transformer ... baseLoad(k:khorizon-1) sum(Pch,1) - sum(Pdis,1) transformerCap; [sol, fval, exitflag] solve(prob); ... end當然這只是示意真正跑起來還需要把掩碼矩陣乘進去并把SOC更新寫成矩陣形式。SOC更新如果放在LP模型內部需要用到過去SOC作為初值我會把當前SOC作為常量寫入而不是作為決策變量否則模型會變成一個動態(tài)非凸問題不能直接用LP求解。2.4 求解器與性能細節(jié)Matlab里solve會自動調用默認的線性規(guī)劃求解器對幾百個變量的LP來說完全足夠。但如果你要跑很多組實驗建議把求解選項固定下來比如設置“最大迭代次數”、“約束容差”避免每次求解都啟動默認邏輯。更快的做法是直接用linprog把目標函數、不等式約束的矩陣和向量手動組裝成標準形式。初期調試用optimproblem更容易定位問題后期性能優(yōu)化再遷移到linprog。我要強調一個容易踩的坑optimvar的維度是nEV x horizon但寫約束時sum(Pch,1)得到的是一個1 x horizon向量而baseLoad可能是行向量也可能是列向量。Matlab對隱式擴展有它的規(guī)則一旦方向不一致要么報維度錯誤要么得到錯誤的約束矩陣。我后來統(tǒng)一把時間維度固定為行向量車輛維度固定為列向量再也沒因為這個出過問題。3. 實操過程與案例實現(xiàn)3.1 算例場景和參數設置為了讓結果可復現(xiàn)我給一個保守但貼近實際的案例。假設配電臺區(qū)變壓器額定容量500kW基礎負荷取典型的夏季工作日形狀高峰集中在傍晚18點到22點。調度周期15分鐘一天96個點。電動汽車共20輛統(tǒng)一采用50kWh電池額定充放電功率7kW充放電效率95%。參數取值調度周期15分鐘預測時域H16步4小時車輛數20電池容量50 kWh最大充放電功率7 kWSOC范圍0.2 ~ 0.9目標SOC0.9峰時電價0.8 元/kWh谷時電價0.3 元/kWh車輛接入時間我隨機分布在8點到20點之間離開時間集中在17點到22點。這樣做能逼出問題很多車下午接入晚上就要離開充電窗口很短還正好撞上電網負荷高峰。如果沒有V2G這一大波充電需求會硬生生疊在負荷曲線峰值上變壓器壓力會非常大。3.2 從數據生成到滾動求解的關鍵代碼我先把基礎負荷和電價生成好用數組保存然后寫一個車輛狀態(tài)更新函數function socNext updateSOC(soc, Pgrid, params) % Pgrid 正表示從電網取電負表示向電網放電 if Pgrid 0 socNext soc params.chargeEff * Pgrid * params.dt / params.batteryCapacity; else socNext soc Pgrid * params.dt / (params.batteryCapacity * params.dischargeEff); end end這里有一個非常重要的設計Pgrid是“和電網交換的功率”所以在放電時電池內部實際消耗的功率是-Pgrid / dischargeEff而不是-Pgrid。這個符號如果搞反SOC更新就會出現(xiàn)“越放電電量越多”的靈異現(xiàn)象。我在調試時遇到過兩次代碼邏輯檢查了半小時才發(fā)現(xiàn)是效率取反。接著是LP模型構建。為了兼顧速度和調試我直接調用linprog把目標函數系數向量f和約束矩陣Aineq、bineq組裝出來。決策變量的排列方式我定為前nEV * H個變量是各周期各車的充電功率后nEV * H個變量是各周期各車的放電功率。這個排列順序一旦確定所有矩陣的列都要按它來。我建議在做矩陣拼裝時先把索引函數寫好比如chIdx (i,t) (t-1)*nEV i; disIdx (i,t) nEV*H (t-1)*nEV i;這樣寫約束時邏輯很直觀不容易索引錯位。3.3 模擬結果怎么讀我對三種策略做了對比無調度車輛一接入就最大功率充電、單向分時充電只能充電不能放電、本文實時調度允許V2G雙向并滾動優(yōu)化。模擬趨勢如下策略峰值負荷kW用戶平均充電費用元平均離網SOC無調度45814.20.82單向分時充電42110.30.90V2G滾動調度3858.60.90這說明V2G對削峰填谷和用戶成本都有明顯作用。峰值負荷下降并不是因為少充了電而是把一部分高峰充電需求前移同時讓部分車輛在傍晚高峰時放電給其他車輛騰出負荷空間。離網SOC在V2G調度下依然能保持0.90說明放電并沒有犧牲用戶出行需求。當然這個表里的數字只是模擬算例不能直接套到其他場景里。如果你把峰谷價差縮小V2G放電收益會明顯下降如果車輛數量增加而變壓器容量不變削峰空間也會跟著受限。所以做項目時一定要自己跑參數敏感性分析而不是照抄別人結論。4. 常見問題與調試心得4.1 四個最容易遇到的求解報錯我調這個項目時報錯類型基本集中在四類整理成速查表報錯或異??赡茉蚪鉀Q辦法“Problem is infeasible”離網前充不到目標SOC或變壓器容量過小導致所有車輛無法同時充電把目標SOC改為軟約束加松弛變量求解成功但SOC越界調度循環(huán)里狀態(tài)更新與模型預測不一致檢查充放電效率正反方向、調度周期單位結果出現(xiàn)大量“既充又放”目標函數缺少懲罰或電價有負值增加放電懲罰項或升級為MILP求解時間隨車輛數急劇上升用optimproblem頻繁創(chuàng)建問題對象改用linprog、避免循環(huán)內重定義峰值負荷反而更高只優(yōu)化費用沒有加入峰負荷約束目標函數中加入峰谷差或變壓器容量的罰項這里面最坑的是第一類。車輛接入時間短、初始SOC低時LP會連一個可行解都找不到。我的解決辦法是把離網SOC約束改成[ SOC_{i,leave} s_i \ge SOC_{i,target} ]其中 (s_i) 是松弛變量同時在目標函數里加入 (M \cdot \sum s_i)M是一個很大的懲罰系數。這樣大部分場景都能求解車輛如果實在充不滿也會以最小的違約程度離開。4.2 實時性優(yōu)化的幾條經驗剛開始我用optimproblem跑20輛車、96個周期整個調度要兩三分鐘才能完成作為離線仿真勉強能忍但離“實時”差得很遠。后來我發(fā)現(xiàn)瓶頸不在求解而在每次循環(huán)里反復構造優(yōu)化問題對象Matlab的面向對象封裝開銷非常大。把核心求解改成linprog之后同樣的算例壓縮到幾十秒。再進一步采用離線預計算索引矩陣、全程向量化效果會更好。有一個更實用的技巧是縮小預測時域如果只做1小時預測也就是4個調度周期計算量會降到原來的四分之一而調度效果因為滾動更新的存在并不會劣化太多。如果你要處理上百輛車我建議直接用稀疏矩陣描述約束因為大部分約束矩陣都是對角塊結構稀疏后會快很多。Matlab里sparse函數的應用很值得花半小時研究。4.3 能量守恒是調試的第一道關卡調度算法寫完了怎么判斷它對不對我的方法只有一個先查能量守恒。把整個調度周期內所有車輛的充電電量加起來減去放電電量再和SOC變化總量對比。對一輛車來說關系是[ \sum_t P_{ch,i,t} \cdot \eta_c - \sum_t \frac{P_{dis,i,t}}{\eta_d} E_i \cdot (SOC_{i,end} - SOC_{i,start}) ]如果左端明顯大于右端說明代碼可能在某個環(huán)節(jié)讓能量憑空產生。最常見的原因是放電效率用反或者SOC更新時用了與LP模型內不同的公式。我記得有一次結果看起來特別好峰谷差小、成本低但車主平均離網SOC只有0.75明顯低于預期。一查發(fā)現(xiàn)是放電懲罰寫成了負的等于系統(tǒng)通過反復放電“賺錢”最后所有車都被放空了。所以做這類項目不能只看目標函數值每個約束的實際狀態(tài)都要在結果里輸出出來檢查。4.4 我的調試工作流我現(xiàn)在的習慣是先跑一個3輛車、2小時的小場景把功率曲線和SOC曲線畫出來逐行確認每輛車的動作都符合預期。小場景沒問題之后再逐步放大車輛數和時間窗口。每做一次改動我都會記錄三個指標總充電量、總放電量、全部車的離網SOC分布。如果這三個指標相對基線有明顯異常就先停下加日志排查而不是繼續(xù)調參數。另外我會保留上一輪調度結果作為對照。有次為了測試新約束我把結果圖和前一天的舊圖疊在一起看一下發(fā)現(xiàn)新代碼在晚上8點后把所有車都強制放電了原因是變壓器約束那里的baseLoad索引寫錯了一位把負荷序列整體左移了一個周期。這類索引錯誤在沒有對照圖時很難一眼看出來。5. 擴展方向與個人體會5.1 從仿真到落地還要補哪些模塊仿真能跑通和真車能上線之間還有很大距離。第一個缺口是通訊延遲和數據質量調度中心拿到的SOC、功率、車輛連接狀態(tài)都不是實時完美的需要考慮測量噪聲和丟包。第二個缺口是預測更新真實電價和負荷預測每隔幾分鐘就會更新滾動優(yōu)化需要接入實時預測接口。第三個缺口是執(zhí)行偏差實際功率指令到達充電樁后可能因為車載協(xié)議限制無法精確執(zhí)行。我目前還在擴展的方向是把光伏發(fā)電的不確定性加進MPC里。當屋頂光伏出力預測有誤差時調度策略需要預留一部分電池容量來應對功率偏差這就變成了隨機模型預測控制。目標函數里還要加“預測偏差導致的不平衡懲罰”比確定性模型復雜不少但更貼近實際園區(qū)綜合能源系統(tǒng)的需求。5.2 代碼工程化與組織建議如果你打算長期在這個項目上迭代我強烈建議把代碼模塊化。我的項目文件分成了三個層次配置層一個params_define.m集中管理所有參數包括車輛數、電池容量、效率、預測時域、懲罰系數。模型層一個build_problem.m根據當前周期和車輛狀態(tài)構建LP或MILP問題返回目標系數和約束矩陣。調度層一個run_scheduler.m負責滾動循環(huán)、狀態(tài)更新、結果存儲。這樣當你需要換一套電價曲線、換一組車輛參數或者換一個求解器時不需要動核心邏輯。我甚至會把目標函數里的懲罰系數也做成數組方便做靈敏度分析時批量修改。存儲結果時我建議把所有中間變量統(tǒng)一寫進一個states結構體每個調度周期往里面追加一條記錄。最后畫圖時只用一段通用的畫圖函數從states里提取數據比在循環(huán)里散落一堆plot要清爽得多。5.3 個人體會這個項目做下來最深的感受是“算法本身不難難在驗證”。市面上很多V2G調度論文展示的效果很漂亮但代碼細節(jié)往往經不起反復推敲。最快的學習方式不是直接套用論文參數而是先寫一個最簡單的規(guī)則調度做基線再上優(yōu)化算法。如果沒有對薄弱基線你會很難判斷峰值負荷下降究竟是因為調度算法有效還是因為某些參數調得順手。我自己踩過多次坑之后現(xiàn)在已經形成習慣每一步改動都留下一個可以回滾的腳本每次實驗都保存結果數據和參數文件。這樣做雖然煩瑣但絕大多數項目里的“玄學問題”最后都能在對比中找到答案。V2G實時調度是一個值得長期深耕的方向如果你也想動手做一遍我建議從今天這個20輛車、15分鐘粒度的案例開始先把能量守恒跑通再逐步疊加預測和不確定性每一步都看得見摸得著比直接啃論文踏實得多。