調(diào)度算法實(shí)戰(zhàn):從目標(biāo)函數(shù)建模到滾動(dòng)時(shí)域優(yōu)化)
簡(jiǎn)介圍繞智能微電網(wǎng)多能互補(bǔ)優(yōu)化調(diào)度提供基于MATLAB實(shí)現(xiàn)的完整算法框架面向電力系統(tǒng)、電氣工程方向的學(xué)生和研究人員可用于解決含光伏、風(fēng)電、儲(chǔ)能及柴油機(jī)組的出力分配問(wèn)題兼顧運(yùn)行成本與碳排放目標(biāo)。壓縮包共20個(gè)文件以18個(gè)MATLAB腳本為主覆蓋目標(biāo)函數(shù)構(gòu)建、粒子群優(yōu)化算法、約束處理與仿真結(jié)果分析等核心環(huán)節(jié)另附自動(dòng)備份文件和Excel數(shù)據(jù)表整體僅19KB結(jié)構(gòu)緊湊便于快速瀏覽。已有530人學(xué)習(xí)瀏覽適合作為智能調(diào)度算法入門的參考范例。通過(guò)仿真腳本可逐步理解種群初始化、迭代尋優(yōu)等實(shí)現(xiàn)細(xì)節(jié)并能基于數(shù)據(jù)表擴(kuò)展自定義負(fù)荷與電價(jià)場(chǎng)景驗(yàn)證不同調(diào)度策略的經(jīng)濟(jì)性與可行性對(duì)學(xué)術(shù)研究、課程設(shè)計(jì)和工程預(yù)研均有實(shí)用價(jià)值。1. 智能微電網(wǎng)調(diào)度算法.zip解壓之后真正值錢的是模型不是代碼拿到“智能微電網(wǎng)調(diào)度算法.zip”這類壓縮包時(shí)先想清楚一個(gè)現(xiàn)實(shí)解壓后會(huì)看到若干 Python 模塊、一堆 CSV 數(shù)據(jù)和一份模型說(shuō)明但文件本身不能直接變成微電網(wǎng)收益。真正值錢的在于目標(biāo)函數(shù)怎么定義、約束條件列了哪幾條、調(diào)度周期取多長(zhǎng)、重算頻率怎么設(shè)。微電網(wǎng)調(diào)度要做的事一句話概括就是在負(fù)荷、光伏出力、儲(chǔ)能狀態(tài)和分時(shí)電價(jià)都已知或可預(yù)測(cè)的前提下逐時(shí)段安排儲(chǔ)能充放電、柴油機(jī)啟停和電網(wǎng)買賣電把綜合運(yùn)行成本降到最低。同一套光儲(chǔ)設(shè)備換一種調(diào)度策略年運(yùn)行費(fèi)用能差出 15% 上下。這篇筆記寫給做光儲(chǔ)充站、園區(qū)微電網(wǎng)能量管理系統(tǒng)EMS的工程和研發(fā)人員從建模選型講到能跑的代碼骨架再集中拆一批反復(fù)出現(xiàn)的調(diào)試坑。2. 調(diào)度建模第一步目標(biāo)函數(shù)、約束條件與參數(shù)設(shè)定決定算法能否落地在敲第一行代碼之前應(yīng)該先把數(shù)學(xué)模型定下來(lái)。調(diào)度算法的工程本質(zhì)是一個(gè)帶約束的最優(yōu)化問(wèn)題在滿足設(shè)備物理限制的前提下選一組逐時(shí)段的運(yùn)行點(diǎn)把目標(biāo)函數(shù)做到最小。建模這一步的取舍直接決定算法包在真實(shí)項(xiàng)目里能不能復(fù)用。絕大多數(shù)落地項(xiàng)目用的是確定性混合整數(shù)線性規(guī)劃MILP為主干再套一層滾動(dòng)優(yōu)化變量規(guī)模只有幾百個(gè)時(shí)開源求解器完全夠用不需要一上來(lái)就上商用求解器。2.1 目標(biāo)函數(shù)設(shè)計(jì)為什么“最低運(yùn)行成本”是主目標(biāo)棄光率只能當(dāng)軟約束先說(shuō)結(jié)論光儲(chǔ)微電網(wǎng)調(diào)度絕大多數(shù)據(jù)項(xiàng)目把目標(biāo)函數(shù)寫成“一天內(nèi)總運(yùn)行成本最小”而不是“新能源利用率最高”。原因是業(yè)主看的是電費(fèi)賬單和投資收益。棄光只要不超過(guò)當(dāng)?shù)乜己司€是可以接受的事多充放一次電池卻要承擔(dān)循環(huán)壽命損耗——電池循環(huán)壽命多數(shù)在八千到一萬(wàn)次量級(jí)每浪費(fèi)一次都是真金白銀。所以第一目標(biāo)是成本新能源消納率放進(jìn)約束條件或者懲罰項(xiàng)里兜底。一個(gè)帶儲(chǔ)能的光儲(chǔ)微電網(wǎng)典型目標(biāo)函數(shù)長(zhǎng)這樣min C Σ_t [ C_buy(t)·P_buy(t)·Δt ? C_sell(t)·P_sell(t)·Δt ] Σ_t [ C_fuel(t)·P_dg(t)·Δt C_op·u_dg(t) ] λ_bat·Σ_t [ α·(P_ch(t) P_dis(t))·Δt ]其中 C_buy 是購(gòu)電價(jià)、C_sell 是上網(wǎng)電價(jià)P_buy 和 P_sell 分別是購(gòu)電功率和賣電功率P_dg 是柴油機(jī)出力u_dg 是啟停 0-1 變量C_op 是單次啟停折算成本最后一項(xiàng)給電池充放電加一個(gè)退化懲罰λ_bat 用元/kWh 表示。這個(gè)公式在工程里有三層含義買賣電價(jià)差驅(qū)動(dòng)峰谷套利柴油機(jī)成本約束只在必要時(shí)才開機(jī)電池退化懲罰壓制無(wú)意義循環(huán)。目標(biāo)函數(shù)里最容易翻車的兩個(gè)地方一個(gè)是把上網(wǎng)電價(jià)設(shè)得跟購(gòu)電價(jià)一樣高求解器會(huì)讓電池在低谷充滿、高峰賣給電網(wǎng)表面上賺了電費(fèi)實(shí)際上把電網(wǎng)當(dāng)成無(wú)限容量電瓶還賠上電池壽命。第二個(gè)是把電池退化成本直接設(shè)為 0結(jié)果求解器會(huì)在每個(gè)峰谷價(jià)差時(shí)段都來(lái)回充放壽命折損遠(yuǎn)大于電費(fèi)收益。所以哪怕沒(méi)有實(shí)測(cè)數(shù)據(jù)標(biāo)定 λ_bat也要給充放兩個(gè)方向各加一個(gè)最小懲罰系數(shù)比如 0.02 元/kWh 起步先把無(wú)意義的來(lái)回充放壓住再慢慢調(diào)。如果項(xiàng)目里還有可平移負(fù)荷比如空調(diào)、充電樁我一般會(huì)在目標(biāo)函數(shù)里加一個(gè)“負(fù)荷平移收益”項(xiàng)優(yōu)先級(jí)放在購(gòu)電成本之后。這樣在電價(jià)尖峰時(shí)段優(yōu)化器會(huì)主動(dòng)切一部分可控負(fù)荷而不是啟動(dòng)柴油機(jī)調(diào)度結(jié)果在客戶那里更容易被接受也減輕了儲(chǔ)能壓力。2.2 約束條件清單功率平衡、SOC 遞推、爬坡與旋轉(zhuǎn)備用約束是模型的骨架也是最容易被寫爛的地方。開始寫代碼前先把約束清單列出來(lái)節(jié)點(diǎn)功率平衡、儲(chǔ)能 SOC 遞推、電池功率限值、電網(wǎng)交換限值、柴油機(jī)爬坡率、旋轉(zhuǎn)備用、SOC 上下限與終值。下面這套是工程中反復(fù)用到的表達(dá)方式按每時(shí)段 Δt 1h 舉例。功率平衡等式約束是所有調(diào)度模型的出發(fā)點(diǎn)P_pv(t) P_dis(t) P_buy(t) P_dg(t) P_load(t) P_ch(t) P_sell(t)含義是一個(gè)節(jié)點(diǎn)上光伏、儲(chǔ)能放電、電網(wǎng)購(gòu)電和柴油機(jī)出力加起來(lái)必須等于負(fù)荷、儲(chǔ)能充電和電網(wǎng)賣電之和。這個(gè)等式在每個(gè)時(shí)段單獨(dú)成立缺失任何一個(gè)時(shí)段都會(huì)讓模型“漏電”。儲(chǔ)能 SOC 遞推關(guān)系到能量的時(shí)間耦合寫成E(t1) E(t) η_ch·P_ch(t)·Δt ? P_dis(t)·Δt / η_dis注意充電效率和放電效率不能合并成一個(gè)系數(shù)否則 SOC 曲線計(jì)算會(huì)樂(lè)觀過(guò)頭。把這套約束整理成一張參數(shù)表方便在配置里逐項(xiàng)確認(rèn)約束或參數(shù)典型表達(dá)式常用取值節(jié)點(diǎn)功率平衡P_pv P_dis P_buy P_dg P_load P_ch P_sell每時(shí)段強(qiáng)制相等儲(chǔ)能 SOC 范圍E_min ≤ E(t) ≤ E_maxE_min 取 0.1~0.2E_maxE_max 取 0.9~0.95E_max充放功率上限P_ch ≤ P_ch_maxP_dis ≤ P_dis_max按容量 0.5C~1C 折算充放電效率0.92~0.96充電、放電分開設(shè)系數(shù)柴油機(jī)爬坡率ΔP_dg ≤ Ramp_rate·Δt30%~100% 額定功率/小時(shí)旋轉(zhuǎn)備用可用備用容量 ≥ 最大單機(jī)容量孤島模式按最大負(fù)荷 10%~15%這里有一個(gè)隱蔽坑充放電效率不對(duì)稱導(dǎo)致“充進(jìn)去 1kWh 需要從電網(wǎng)拿超過(guò) 1kWh”的損耗必須在遞推公式里顯式寫出來(lái)。不少初版代碼把效率都當(dāng)成 1.0結(jié)果夜里充滿、白天放空調(diào)度收益多算一大截客戶一核對(duì)電表就對(duì)不上。我習(xí)慣在 E(t1) 那行單獨(dú)加注釋并在報(bào)表里同時(shí)輸出“SOC 理論值”和“BMS 實(shí)際采樣值”后面做偏差分析就有依據(jù)。另一個(gè)關(guān)鍵點(diǎn)是終值 SOC。不少第一次做調(diào)度的人只約束了 SOC 上下限忘了 E(T) 要和 E(0) 一致于是求解器在最后時(shí)段把電池全部放空——多余電量對(duì)目標(biāo)函數(shù)沒(méi)有任何貢獻(xiàn)。這個(gè)問(wèn)題在 4.1 節(jié)專門講建模時(shí)先記住終值變量必須出現(xiàn)在約束矩陣?yán)锓駝t調(diào)度結(jié)果沒(méi)法銜接第二天。2.3 輸入數(shù)據(jù)加工負(fù)荷曲線、光伏出力、分時(shí)電價(jià)如何對(duì)到同一時(shí)間軸輸入數(shù)據(jù)的質(zhì)量比求解器更重要。加工第一步是統(tǒng)一時(shí)間軸負(fù)荷通常來(lái)自電表或能耗平臺(tái)15 分鐘或 30 分鐘一條光伏預(yù)測(cè)有些按 1 小時(shí)間隔給有些按 15 分鐘分時(shí)電價(jià)常常由營(yíng)銷系統(tǒng)按月下發(fā)只有峰平谷三個(gè)價(jià)段。調(diào)度周期是 24 小時(shí)、步長(zhǎng) 1 小時(shí)時(shí)三類數(shù)據(jù)全部要重采樣到同一個(gè)時(shí)間網(wǎng)格并按本地時(shí)區(qū)標(biāo)好時(shí)間戳否則矩陣裝配階段就埋下了不收斂或錯(cuò)位的隱患。我做數(shù)據(jù)清洗時(shí)按這套順序處理負(fù)荷曲線里明顯小于 0 或超過(guò)電表滿度值 2 倍的記錄直接剔除連續(xù)缺失不超過(guò)三個(gè)時(shí)間點(diǎn)的做線性插值不要用均值填因?yàn)榫禃?huì)抹掉負(fù)荷的真實(shí)波動(dòng)。光伏數(shù)據(jù)同理但夜間 0 點(diǎn)數(shù)據(jù)不要一律當(dāng)異常刪掉有些逆變器夜間有自耗電讀數(shù)刪了反而破壞了逐時(shí)連續(xù)性。分時(shí)電價(jià)則要把峰平谷三個(gè)價(jià)段展開成逐時(shí)數(shù)組再注意電價(jià)尖峰段前后各留出幾個(gè)小時(shí)的緩沖時(shí)段——儲(chǔ)能需要在尖峰到來(lái)前完成充電這是調(diào)度算法產(chǎn)生收益的基本盤。參數(shù)單位也要單獨(dú)核對(duì)一遍功率用 kW電量用 kWh電價(jià)用元/kWh。常見(jiàn)問(wèn)題是光伏預(yù)測(cè)數(shù)據(jù)給的是 MW負(fù)荷數(shù)據(jù)是 kW放進(jìn)同一個(gè)公式里差三個(gè)數(shù)量級(jí)最后結(jié)果全錯(cuò)。我會(huì)在讀取函數(shù)里做一次量綱校驗(yàn)例如 load.max() 小于 100 就提示“疑似 MW/kW 混用”避免問(wèn)題留到出報(bào)表階段才發(fā)現(xiàn)。3. 從 zip 到可運(yùn)行求解器搭建最小調(diào)度器和它的輸入輸出接口3.1 先檢查壓縮包目錄入口文件、依賴清單和數(shù)據(jù)文件的位置拿到包別急著解壓運(yùn)行。先把解壓后的根目錄列出來(lái)確認(rèn)有沒(méi)有入口文件、依賴說(shuō)明和樣例數(shù)據(jù)。常見(jiàn)打包方式是直接把整個(gè)工程根目錄壓成 zip也就是類似在 Linux 下壓縮當(dāng)前文件夾到 zip 的習(xí)慣解壓后多一層嵌套目錄是正常的所以先看結(jié)構(gòu)再定入口unzip -q 智能微電網(wǎng)調(diào)度算法.zip -d mg_scheduler cd mg_scheduler tree -L 2 # 沒(méi)有 tree 就執(zhí)行: find . -maxdepth 3 -type f | head -50解壓后檢查四個(gè)位置入口腳本、依賴清單、數(shù)據(jù)目錄、輸出目錄。整理成檢查表文件或目錄作用檢查點(diǎn)main.py 或 run_scheduler.py程序入口能否在無(wú) GUI 環(huán)境下直接跑通requirements.txtPython 依賴是否有版本號(hào)例如 scipy1.10config.yaml 或 config.json參數(shù)配置看調(diào)度時(shí)長(zhǎng) T、時(shí)間粒度 Δt、求解器選項(xiàng)data/輸入的負(fù)荷、光伏、電價(jià)看時(shí)間戳范圍和單位output/調(diào)度結(jié)果輸出確認(rèn)不會(huì)被默認(rèn)覆蓋如果壓縮包沒(méi)有帶 requirements.txt就自己建虛擬環(huán)境把 scipy、pandas、numpy 這幾個(gè)基礎(chǔ)庫(kù)裝好再記錄版本號(hào)。調(diào)度算法對(duì)庫(kù)版本確實(shí)敏感尤其 scipy 從 1.9 之后 linprog 的默認(rèn)求解器換成了 highs同樣的模型在舊版本上跑出的結(jié)果可能不一樣。所以“任何代碼包跑得起來(lái)”之前先讓運(yùn)行環(huán)境可復(fù)現(xiàn)。3.2 最小線性規(guī)劃調(diào)度器一段可運(yùn)行的參考代碼下面給出一個(gè)用 scipy.optimize.linprog 實(shí)現(xiàn)的最小光儲(chǔ)調(diào)度器24 小時(shí)、1 小時(shí)間隔變量只含購(gòu)電、賣電、儲(chǔ)能充放功率和電池 SOC。這是最精簡(jiǎn)的線性規(guī)劃骨架沒(méi)有使用 0-1 整數(shù)變量適合先跑通數(shù)據(jù)鏈路和檢驗(yàn)約束矩陣。# 最小光儲(chǔ)調(diào)度器24 小時(shí)儲(chǔ)能充放優(yōu)化線性規(guī)劃 # 變量排列順序 # P_buy[0..T-1], P_sell[0..T-1], P_ch[0..T-1], P_dis[0..T-1], E[0..T] import numpy as np from scipy.optimize import linprog T 24 # 負(fù)荷、光伏出力單位 kW現(xiàn)場(chǎng)數(shù)據(jù)可從 CSV 讀取 load np.array([340,320,300,280,260,280,340,420,520,580,560,490, 480,470,460,520,560,540,500,460,430,390,360,350]) pv np.array([0,0,0,0,20,50,100,180,260,320,340,330, 310,280,240,180,120,60,20,0,0,0,0,0]) # 分時(shí)購(gòu)電價(jià)元/kWh這里用簡(jiǎn)化的峰谷平序列 price_buy np.array([0.8]*4 [1.2]*8 [0.6]*6 [1.2]*4 [0.8]*2) price_sell price_buy * 0.6 # 上網(wǎng)電價(jià)按購(gòu)電價(jià)的 60% # 儲(chǔ)能參數(shù) E_max, E_min 500.0, 100.0 # SOC 對(duì)應(yīng)電量上下限單位 kWh E_start E_end 300.0 # 起止 SOC 電量防止末時(shí)段放空 eta_ch, eta_dis 0.95, 0.95 # 充電、放電效率 P_ch_max P_dis_max 150.0 # 充放功率上限kW P_grid_max 500.0 # 與電網(wǎng)交換功率上限kW n 4 * T (T 1) s_buy, s_sell, s_ch, s_dis, s_E 0, T, 2 * T, 3 * T, 4 * T # 目標(biāo)函數(shù)購(gòu)電成本減去賣電收益 c np.zeros(n) c[s_buy:s_buy T] price_buy c[s_sell:s_sell T] -price_sell A_eq, b_eq [], [] # 節(jié)點(diǎn)功率平衡P_buy P_dis pv P_load P_sell P_ch for t in range(T): row np.zeros(n) row[s_buy t] 1.0 row[s_dis t] 1.0 row[s_sell t] -1.0 row[s_ch t] -1.0 A_eq.append(row) b_eq.append(load[t] - pv[t]) # 儲(chǔ)能能量遞推E(t1) - E(t) eta_ch*P_ch - P_dis/eta_dis for t in range(T): row np.zeros(n) row[s_E t 1] 1.0 row[s_E t] -1.0 row[s_ch t] -eta_ch row[s_dis t] 1.0 / eta_dis A_eq.append(row) b_eq.append(0.0) # 初始與終值 SOC各自作為一條等式約束 row0 np.zeros(n) row0[s_E] 1.0 A_eq.append(row0) b_eq.append(E_start) rowT np.zeros(n) rowT[s_E T] 1.0 A_eq.append(rowT) b_eq.append(E_end) # 變量上下界 bounds ([(0.0, P_grid_max)] * T # P_buy [(0.0, P_grid_max)] * T # P_sell [(0.0, P_ch_max)] * T # P_ch [(0.0, P_dis_max)] * T # P_dis [(E_min, E_max)] * (T 1)) # E res linprog(c, A_eqA_eq, b_eqb_eq, boundsbounds, methodhighs) if res.success: x res.x print(最小運(yùn)行成本, round(res.fun, 2), 元) p_buy x[s_buy:s_buy T] p_sell x[s_sell:s_sell T] p_ch x[s_ch:s_ch T] p_dis x[s_dis:s_dis T] soc x[s_E:s_E T 1] / E_max print(末時(shí)段 SOC, round(soc[-1], 3)) else: print(無(wú)解, res.message)這段代碼的邏輯可以拆成四塊看。先是變量切片把 P_buy、P_sell、P_ch、P_dis、E 按順序切到一維向量 x 的不同區(qū)段后面每行約束都靠這個(gè)區(qū)段定位不會(huì)串位。然后是功率平衡約束一行對(duì)應(yīng)一個(gè)小時(shí)行里只有五個(gè)位置非零。儲(chǔ)能遞推約束把時(shí)間耦合寫進(jìn)矩陣E 出現(xiàn)了 25 個(gè)變量0 到 24其中 E(0) 和 E(24) 由兩條獨(dú)立的等式固定。最后 linprog 用 highs 方法求解它比老方法快一個(gè)數(shù)量級(jí)也幾乎不需要調(diào)參。參數(shù)上要理解三個(gè)關(guān)鍵點(diǎn)E_end 固定為 300 kWh也就是 60% SOC防止優(yōu)化器把電池在最后一個(gè)時(shí)段放電“清倉(cāng)”充放電效率直接進(jìn)入遞推公式效率不對(duì)稱帶來(lái)的損耗會(huì)在 SOC 曲線上顯現(xiàn)P_ch_max 和 P_dis_max 取 150 kW對(duì)應(yīng) 0.3C 的充放倍率實(shí)際項(xiàng)目要以儲(chǔ)能廠商提供的倍率曲線為準(zhǔn)不要按產(chǎn)品銘牌最大值硬填。跑通這段代碼后把 price_buy 拉成明顯峰谷差觀察 SOC 曲線是否先降后升基本就知道約束矩陣有沒(méi)有寫錯(cuò)。3.3 接口設(shè)計(jì)CSV 輸入、JSON 下發(fā)調(diào)度結(jié)果的落庫(kù)與回讀代碼核心能跑了下一步是確定輸入輸出接口。輸入側(cè)我習(xí)慣固定一個(gè) CSV 格式約定列名和單位import pandas as pd # 輸入文件約定列名 # ts, load_kw, pv_kw, buy_price, sell_price df pd.read_csv(data/forecast.csv, parse_dates[ts]) load df[load_kw].to_numpy(float) pv df[pv_kw].to_numpy(float) price_buy df[buy_price].to_numpy(float) price_sell df[sell_price].to_numpy(float)這個(gè)格式的好處是光伏預(yù)測(cè)程序、負(fù)荷預(yù)測(cè)程序、電價(jià)系統(tǒng)各出各的 CSV調(diào)度主程序只管合并互不耦合。輸出側(cè)則要區(qū)分兩個(gè)對(duì)象給人看的報(bào)表用 CSV 或 Excel給微電網(wǎng)控制器執(zhí)行的下發(fā)指令用 JSON。實(shí)際項(xiàng)目里控制器經(jīng)常是 C/C 寫的JSON 比 CSV 更好解析也更容易在 Wedge 接口直接傳。{ version: 1.0, horizon: 24, interval_sec: 3600, timestamp_from: 2025-03-17T00:00:0008:00, plan: [ {h: 0, p_buy_kw: 120, p_sell_kw: 0, p_ch_kw: 0, p_dis_kw: 0, soc: 0.60}, {h: 1, p_buy_kw: 105, p_sell_kw: 0, p_ch_kw: 45, p_dis_kw: 0, soc: 0.68} ] }我給控制器下發(fā)時(shí)json 里的字段全部帶單位后綴比如 p_buy_kw、p_ch_kw避免接收端猜單位。同時(shí)把 SOC 預(yù)報(bào)一并下發(fā)控制器端看到 SOC 與本地 BMS 差異超過(guò) 5% 時(shí)可以主動(dòng)暫停執(zhí)行并請(qǐng)求重新計(jì)算這樣整套系統(tǒng)的數(shù)據(jù)鏈路才閉環(huán)。接口設(shè)計(jì)的另一個(gè)容易被忽略的點(diǎn)是時(shí)區(qū)。輸入 CSV 的時(shí)間戳如果不帶時(shí)區(qū)默認(rèn)被 pandas 解析成什么取決于機(jī)器本地設(shè)置。調(diào)度結(jié)果一旦跨省部署時(shí)區(qū)錯(cuò)位就是“凌晨調(diào)峰計(jì)劃跑到了白天”這類奇怪問(wèn)題的根源。所以接口規(guī)范里明確寫輸入按本地時(shí)間輸出 JSON 必須帶 UTC 偏移也就是 08:00 這樣的顯式標(biāo)注。4. 調(diào)度算法排查避坑結(jié)果不合理的 5 個(gè)隱蔽原因與修復(fù)記錄4.1 電池在電價(jià)低谷反而放電或末時(shí)段同步放空目標(biāo)函數(shù)缺了 SOC 終值約束現(xiàn)象調(diào)度結(jié)果出來(lái)夜里電價(jià)低谷時(shí)電池對(duì)負(fù)荷放電白天電價(jià)尖峰時(shí)卻沒(méi)什么動(dòng)作或者最后幾個(gè)時(shí)段電池集中放電SOC 一路壓到下限??蛻艨吹竭@個(gè)結(jié)果的第一反應(yīng)是說(shuō)模型不對(duì)但代碼本身往往沒(méi)有語(yǔ)法錯(cuò)誤純粹是目標(biāo)函數(shù)里少了一個(gè)約束。原因分兩層。第一層是沒(méi)有把 E(T) 固定下來(lái)整個(gè)調(diào)度周期結(jié)束時(shí)電池里剩多少電都不會(huì)影響成本函數(shù)求解器自然選擇把儲(chǔ)能在最后時(shí)段全部放掉。第二層是預(yù)測(cè)窗口邊界效應(yīng)模型只關(guān)心窗口內(nèi)的收益不關(guān)心窗口結(jié)束后下一階段能不能銜接。這個(gè)問(wèn)題在 24 小時(shí)調(diào)度里尤其突出因?yàn)槊刻?23:00 之后電價(jià)往往處于低位求解器更愿意把電池留著“不充不放”。解決在約束矩陣?yán)镲@式加入 E(24) E(0)或?qū)懗?E_final 30%~60% SOC 的范圍約束。貼上第 3 節(jié)代碼里的兩條行就是標(biāo)準(zhǔn)做法。如果更復(fù)雜些可以把電池退化懲罰 λ_bat 調(diào)高一點(diǎn)讓優(yōu)化器意識(shí)到“無(wú)收益的充放也是在燒壽命”。改完再跑一遍看 SOC 曲線末尾是否平滑回到起始值而不是貼在下邊界上。4.2 換一個(gè)天氣數(shù)據(jù)當(dāng)天結(jié)果就劇烈跳變預(yù)測(cè)誤差被線性規(guī)劃放大現(xiàn)象光伏預(yù)測(cè)從 300 kW 變成 290 kW只差 3%電池卻從“白天充滿”翻成“白天不充”。客戶覺(jué)得模型瘋了搞了個(gè)不穩(wěn)定系統(tǒng)。原因線性規(guī)劃的最優(yōu)解總是在可行域的頂點(diǎn)上一個(gè)小幅度的參數(shù)擾動(dòng)會(huì)讓最優(yōu)解跳到另一個(gè)頂點(diǎn)產(chǎn)生跳變。這是線性規(guī)劃本身的靈敏度特征不是代碼 bug。調(diào)度計(jì)劃本質(zhì)上是一條折線路徑邊界條件一變折點(diǎn)全部重新分配。解決把問(wèn)題拆成兩個(gè)手段并行。第一是滾動(dòng)調(diào)度——把預(yù)測(cè)時(shí)間窗口切成多個(gè)斷面每次只執(zhí)行第一時(shí)段下一時(shí)段重新優(yōu)化這在第 5 章展開。第二是給電池充放功率增加最小持續(xù)時(shí)段約束避免優(yōu)化器在臨界點(diǎn)上來(lái)回翻轉(zhuǎn)最小持續(xù)時(shí)段通常取 1~2 小時(shí)對(duì)磷酸鐵鋰儲(chǔ)能完全夠用。如果項(xiàng)目對(duì)穩(wěn)定性更敏感可以換二次規(guī)劃目標(biāo)函數(shù)把“充放功率與上一輪計(jì)劃值的偏差平方”作為一項(xiàng)代價(jià)是求解速度變慢但換來(lái)的平滑度往往值得。4.3 求解器報(bào) infeasible先查這三處約束自相矛盾現(xiàn)象linprog 返回 status 2提示 “The problem is infeasible”數(shù)據(jù)量一變大就直接無(wú)解。很多人第一反應(yīng)是調(diào)整邊界或者換求解器其實(shí)大部分情況是約束之間互相矛盾。原因最典型的有三處。第一處是 SOC 終值和充電機(jī)會(huì)沖突光伏預(yù)測(cè)為零、負(fù)荷又高、電網(wǎng)限值是 200 kW但 E_end 要求 80% SOC而 E_start 只有 20%電池根本沒(méi)機(jī)會(huì)在 24 小時(shí)里充到目標(biāo)值模型當(dāng)然無(wú)解。第二處是功率平衡等式符號(hào)寫反P_ch 寫到等式另一側(cè)能量遞推不自洽相鄰時(shí)段 SOC 無(wú)法銜接。第三處是爬坡約束和功率上限沖突柴油機(jī)從 0 爬到額定但爬坡率折算出來(lái)的步長(zhǎng)不允許它在一個(gè)時(shí)段內(nèi)跨越那么大范圍。解決按三步排查。先把 SOC 終值約束放開跑一次看最末時(shí)段 SOC 落在哪貼下限說(shuō)明終值定太高貼上限說(shuō)明終值定太低。然后把 A_eq 逐行打出來(lái)從功率平衡行開始核對(duì)符號(hào)確認(rèn) P_buy P_dis pv P_load P_sell P_ch 沒(méi)有寫反。最后給關(guān)鍵約束加松弛變量比如 SOC 上下限允許 5% 的越限但要付出高成本這樣即使輸入數(shù)據(jù)出現(xiàn)異常模型也不會(huì)直接死掉而是給出一個(gè)“帶懲罰的最優(yōu)解”。4.4 孤島運(yùn)行一次大負(fù)荷擾動(dòng)就掉電靜態(tài)調(diào)度計(jì)劃缺了秒級(jí)響應(yīng)現(xiàn)象并網(wǎng)模式下調(diào)度結(jié)果完美但電網(wǎng)斷電切到孤島運(yùn)行后一個(gè)大負(fù)荷啟動(dòng)頻率跌落電池沒(méi)有及時(shí)跟上最后保護(hù)跳閘??蛻裟谜{(diào)度算法包說(shuō)事其實(shí)這里不是調(diào)度算法的區(qū)間是它沒(méi)有負(fù)責(zé)的區(qū)間。原因優(yōu)化求解器給出的是分鐘級(jí)調(diào)度計(jì)劃不是毫秒級(jí)和秒級(jí)控制指令。靜態(tài)調(diào)度算法默認(rèn)設(shè)備按指令瞬時(shí)響應(yīng)沒(méi)有考慮下垂控制和一次調(diào)頻。孤島模式下全網(wǎng)電壓和頻率要靠?jī)?chǔ)能變流器在幾十毫秒內(nèi)建起來(lái)調(diào)度算法計(jì)算出“這一小時(shí)電池出 150 kW”但 150 kW 是分鐘級(jí)平均剛起動(dòng)的異步電機(jī)在 0.5 秒內(nèi)的瞬時(shí)沖擊可能是它三倍這個(gè)尺度調(diào)度計(jì)劃覆蓋不到。解決把調(diào)度算法輸出的結(jié)果作為上層參考值實(shí)際執(zhí)行放在儲(chǔ)能變流器PCS本地做 V/f 下垂控制也就是讓 PCS 自己感知頻率偏差并調(diào)整輸出。同時(shí)調(diào)度模型里增加旋轉(zhuǎn)備用約束孤島模式下要求可用備用容量大于最大單機(jī)容量或者最大負(fù)荷的 10%~15%。做整個(gè)系統(tǒng)集成時(shí)調(diào)度計(jì)劃、PCS 下垂參數(shù)、保護(hù)定值三者的接口要提前定義好算法包解決經(jīng)濟(jì)性問(wèn)題物理層解決安全問(wèn)題邊界畫清楚才不容易背鍋。4.5 zip 解壓報(bào) invalid zip archive先看 EOCD 再看文件長(zhǎng)度現(xiàn)象解壓到一半報(bào) “invalid zip archive: could not find EOCD”或者 Windows 下提示壓縮文件已損壞。很多人以為是解壓工具的問(wèn)題其實(shí)通常是文件已經(jīng)斷了。原因EOCDEnd of Central Directory是 zip 文件末尾的目錄結(jié)束標(biāo)記。文件在傳輸過(guò)程中被截?cái)嗷蛘邚木W(wǎng)盤下載中斷重試后拼接出錯(cuò)尾部幾個(gè)字節(jié)丟失解壓器就找不到 EOCD。另一個(gè)常見(jiàn)場(chǎng)景是郵件和即時(shí)通訊工具對(duì)附件壓縮重編碼時(shí)任務(wù)列表里看起來(lái)是 .zip實(shí)際內(nèi)容已經(jīng)被改成別的格式。解決按順序做三件事。先對(duì)比本地文件大小和來(lái)源說(shuō)明中的大小對(duì)不上就直接重新下載別浪費(fèi)時(shí)間。再用 unzip -t 做完整性測(cè)試輸出里有 “CRC OK” 才算沒(méi)壞出現(xiàn) “bad CRC” 說(shuō)明文件數(shù)據(jù)區(qū)已經(jīng)損壞也要重下。如果只有一份損壞文件且沒(méi)有重新下載的渠道可以用 7-Zip 的修復(fù)功能重建目錄但修復(fù)后解壓出的核心數(shù)據(jù)必須重新校驗(yàn)——尾部損壞可能丟最后時(shí)段的時(shí)間標(biāo)簽修復(fù)得回來(lái)的是文件修復(fù)不回來(lái)的是數(shù)據(jù)完整性。最穩(wěn)妥的做法是找打包方重新生成順手把項(xiàng)目版本號(hào)寫進(jìn) zip 文件名避免二次比對(duì)時(shí)又拿錯(cuò)版本。5. 進(jìn)階用法把離線調(diào)度升級(jí)為滾動(dòng)時(shí)域調(diào)度并量化經(jīng)濟(jì)收益全天一次性下發(fā)調(diào)度計(jì)劃是很多算法包的默認(rèn)行為但在現(xiàn)場(chǎng)往往撐不住。負(fù)荷數(shù)據(jù)上傳有延遲光伏預(yù)測(cè)偏差一累積第二個(gè)小時(shí) SOC 就和模型算出來(lái)的差出 5%~8%。把離線調(diào)度改成滾動(dòng)時(shí)域調(diào)度Receding Horizon Control是相對(duì)低成本又立竿見(jiàn)影的升級(jí)路徑。5.1 滾動(dòng)窗口最小改造與回測(cè)驗(yàn)證核心思路是每個(gè)調(diào)度時(shí)點(diǎn)重新讀取當(dāng)前實(shí)際狀態(tài)以滾動(dòng)窗口做下一輪優(yōu)化但只執(zhí)行第一個(gè)時(shí)段的計(jì)劃執(zhí)行完再滑到下一個(gè)時(shí)點(diǎn)。最小骨架如下# 滾動(dòng)時(shí)域調(diào)度的最小骨架每次窗口重新求解只應(yīng)用第一個(gè)時(shí)段 for step in range(sim_len): window_data fetch_window(t_step, horizon) # 取 [t_step, t_stepT] 數(shù)據(jù) plan solve_one_window(window_data) # 調(diào)用 3.2 節(jié)的 LP 求解器 apply_setpoint(plan.iloc[0]) # 只下發(fā)第一時(shí)段 t_step 1窗口長(zhǎng)度一般取 8~24 小時(shí)重算頻率取 15 分鐘到 1 小時(shí)。要注意的是每次滾動(dòng)開始前要從 BMS 或現(xiàn)場(chǎng)數(shù)據(jù)重新讀取實(shí)際 SOC 作為初始值而不是沿用上一輪預(yù)測(cè)的末端 SOC否則誤差會(huì)一天天累積。窗口越短抗預(yù)測(cè)偏差的能力越強(qiáng)但對(duì)底層控制系統(tǒng)的執(zhí)行精度要求也越高。驗(yàn)證經(jīng)濟(jì)性最直接的方法是回測(cè)拿過(guò)去一個(gè)月的負(fù)荷、光伏和電價(jià)真實(shí)數(shù)據(jù)分別跑離線全天計(jì)劃和滾動(dòng)窗口計(jì)劃統(tǒng)計(jì)購(gòu)電總成本、賣電總收益、電池循環(huán)電量這三個(gè)指標(biāo)。滾動(dòng)計(jì)劃通常能省 2%~8% 的運(yùn)行成本如果回測(cè)結(jié)果反而更貴優(yōu)先懷疑預(yù)測(cè)數(shù)據(jù)是否有系統(tǒng)性偏差比如光伏預(yù)測(cè)整體偏高 20%這會(huì)讓滾動(dòng)計(jì)劃的窗口邊界一直處于錯(cuò)誤預(yù)期之中。我在前一個(gè)項(xiàng)目里的習(xí)慣是先把“計(jì)劃下發(fā)”和“狀態(tài)回讀”的鏈路跑通再談優(yōu)化模型本身。調(diào)度算法包再漂亮落地時(shí)第一個(gè)月要用人工抽查數(shù)據(jù)和實(shí)際執(zhí)行等偏差穩(wěn)定在可接受區(qū)間了再切換到自動(dòng)滾動(dòng)模式。這是我吃過(guò)虧后養(yǎng)成的習(xí)慣希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取