
1. 分布式電源入網后傳統故障定位為什么開始“失靈”1.1 傳統配電網故障定位的基本假設在談分布式電源Distributed Generation, DG的影響之前先把傳統配電網故障定位的老底摸清楚。常規的配電網大多是單電源、輻射狀結構潮流方向固定從變電站母線流向各饋線末端。基于這個前提故障定位可以簡化成一個非常樸素的思路短路電流從系統側流向故障點那么只要檢測到哪一段饋線上流過故障電流再結合開關狀態信息就能把故障區間圈定出來。這個思路落到工程上最典型的就是基于饋線終端單元FTU上報的故障電流過流標志配合故障指示器信號在配電自動化主站里用矩陣算法或者直接查表法完成定位。矩陣算法的原理很直觀把配電網抽象成節點支路關聯矩陣FTU上報的過流信息構成一個信息向量通過矩陣運算把故障區域反推出來。這個算法在饋線長度短、分支多、負荷分散的傳統配電網中運行得很穩定因為它背后的假設只有兩條第一故障電流一定來自系統側變電站第二非故障區段流過的故障電流方向一致不會出現多源供電導致的電流倒送。1.2 DG接入打破了哪些“潛規則”分布式電源大規模接入以后上述兩條假設都不成立了。首先DG本身就是一個電源點。當饋線上某個位置發生故障除了系統側變電站會提供短路電流外DG也會在故障持續期間比如在逆變器控制策略退出或低電壓穿越保護動作之前向故障點注入短路電流。這樣一來故障點兩側都有可能檢測到過流信號傳統矩陣算法中“電源側到故障點之間所有開關上報過流”的判斷規則就直接失效。其次DG位置的隨機性和出力波動性讓故障電流分布更加復雜。如果DG接入容量較大或者并網點恰好位于故障點與系統電源之間那么流過部分非故障區段的電流可能會反向甚至接近零造成FTU漏報或誤報。更麻煩的是如果同一饋線上接入了多個DG故障電流的分布還取決于各DG的故障穿越特性——有的光伏逆變器在電壓跌落后幾百毫秒內就會脫網而有的具備低電壓穿越能力會持續提供限幅電流這個時間窗口內的信息就特別不穩定。我見過不少仿真數據同樣的故障點同樣的網絡參數只改變DG并網容量傳統矩陣算法給出的定位結果可能從正確區間直接漂移到相鄰饋線段上。問題不復雜但是影響很直接故障定位錯了隔離操作就會出錯非故障區域跟著停電故障時間被拉長甚至可能引發DG脫網后的電壓頻率波動。所以分布式電源對配電網故障定位的影響本質上不是某一個算法環節出了問題而是整個基于單源輻射狀網絡假設的定位邏輯需要重新校準。指望靠簡單打個補丁、調個閾值就解決不太現實。2. 解析故障定位的核心數學模型從饋線區段到開關函數2.1 開關函數怎么描述故障信息要編程實現先把模型講清楚。配電網饋線故障定位通常把網絡看作若干個區段Section連接而成區段之間用斷路器、分段開關或聯絡開關連接。每個FTU采集它所在開關位置的故障電流方向信息簡化為二進制狀態有過流且方向指向故障區段則計1無過流或方向背向故障計0。為了處理DG接入后的雙向潮流有的方案用三位狀態正向過流、反向過流、無過流不過這里先從最常用的二進制模型開始。定義n個饋線區段的狀態變量為 (x_i \in {0, 1})代表第i個區段是否故障1表示故障。定義m個開關測點的上報信息為 (I_j \in {0, 1})代表FTU檢測到過流。傳統矩陣算法通過構建網絡關聯矩陣用矩陣運算反解 (x)。但多DG接入后(I_j) 不再是 (x) 的簡單確定性映射因為DG注入電流會改變某些區段的實際過流狀態。更工程化的做法是引入開關函數的概念。開關函數的含義是對于第j個開關根據某一假定的區段故障狀態向量 (x)推算該開關理論上應檢測到的過流狀態 (I_j^*(x))。這個推算要考慮網絡拓撲、系統電源位置以及所有DG的出力位置。[ I_j^*(x) \left( \bigvee_{k \in S_{sys,j}} x_k \right) \oplus \left( \bigvee_{k \in S_{DG,j}} x_k \right) ]其中 (S_{sys,j}) 是系統電源到開關j之間經過的區段集合(S_{DG,j}) 是所有DG到開關j之間經過的區段集合。(\vee) 表示邏輯或(\oplus) 表示異或。這個公式的意思很直白如果系統電源和DG分別在開關兩側且某一側有故障那么開關檢測到過流如果兩側都有故障區段電流方向可能被抵消或反向此時要結合方向信息修正。實際編程中我不會直接用邏輯表達式而是用圖遍歷來生成每個開關與各電源之間的連通路徑。網絡拓撲用鄰接表存儲系統電源和DG都當作帶方向的電源節點遍歷時記錄路徑上的區段編號。2.2 評價函數與定位判據有了開關函數故障定位就成了一個優化問題尋找一個最優的故障區段假設 (x^)使得所有FTU上報的實測信息 (I_j^{meas}) 與理論推算 (I_j^(x)) 最一致。定義評價函數適應度函數[ F(x) \sum_{j1}^{m} \left( I_j^{meas} \oplus I_j^*(x) \right) ]這個函數統計的是“不一致”的開關數。(F(x)0) 說明這個故障假設完全解釋了所有FTU上報信息屬于完美匹配但實際中由于FTU漏報、誤報以及DG故障電流的不確定性(F(x)) 通常不為0需要尋找使 (F(x)) 最小的 (x)。為了懲罰多解情況常見的改進是在適應度函數里加一個“故障區段數量”的正則項[ F_{fit}(x) \sum_{j1}^{m} \left( I_j^{meas} \oplus I_j^*(x) \right) w \cdot \sum_{i1}^{n} x_i ]其中 (w) 是權重系數一般取0.3~0.8。加正則項的原因是當DG數量較多時可能出現多個假定的故障區段組合得到相同的FTU不一致數如果不加懲罰定位程序可能會報出多個孤立的“偽故障點”無從選擇。這個思路和機器學習里加L1正則強迫稀疏解是同一回事。有了這個優化模型剩下的問題就是怎么高效求解。區段數量少比如10個以內時可以直接窮舉所有 (2^n) 種組合但配電網一個饋線組動輒三四十個區段窮舉不現實所以需要啟發式算法。3. 改進算法與Python實現基于GA的容錯定位方案3.1 為什么選遺傳算法而不直接窮舉遺傳算法GA在配電網故障定位里是老面孔了。原因有三個一是它天然處理二進制編碼區段故障狀態正好用0/1染色體表示二是不需要求導不依賴目標函數的連續性適應度函數就算是分段常數也沒關系三是多DG場景下問題可能有多峰特性GA的種群搜索方式比梯度類方法更不容易陷入局部最優。我知道有人會拿粒子群PSO或者差分進化DE說事說收斂更快。但在工程實踐中配電網故障定位的區段數一般不超過100GA慢一點也就幾十毫秒的事而且GA的交叉變異操作對于離散二進制問題有明確的語義——交叉相當于交換不同區段的故障假設變異相當于嘗試改變某個區段的故障狀態調試起來非常直觀。所以我最終選了GA作為示例。代碼結構上我做了一個可復用的類核心輸入是網絡的圖結構、DG位置參數和FTU上報信息。下面這份代碼不是完整工業代碼但目標是把整個定位鏈路跑通。3.2 完整Python代碼與參數說明import numpy as np import networkx as nx import random class DistributionNetwork: 配電網拓撲描述用networkx有向圖表示 - 節點: 開關/母線/電源/饋線端點 - 邊: 饋線區段每條邊帶唯一id - 電源節點: 系統電源 sys_source, DG節點用前綴 DG def __init__(self): self.graph nx.DiGraph() self.sections [] # 區段列表元素為 (u,v,section_id) self.sys_source S self.dg_nodes [] self.ftu_map {} # section_id - FTU測點編號 def add_section(self, u, v, section_id, has_ftuTrue): self.graph.add_edge(u, v, section_idsection_id) self.sections.append((u, v, section_id)) if has_ftu: ftu_id len(self.ftu_map) self.ftu_map[section_id] ftu_id def add_dg(self, node): self.dg_nodes.append(node) self.graph.add_node(node) def paths_from_source(self, source, target_section): 計算從某個電源source出發經過target_section所在邊的路徑信息 返回該電源與區段的位置關系: 電源在區段首端/末端/不連通 u, v, sec_id None, None, None for su, sv, ss in self.sections: if ss target_section: u, v, sec_id su, sv, ss break if u is None: return None # 電源到區段首端 try: path_to_u nx.shortest_path(self.graph, source, u) return (source_before_section, sec_id) except nx.NetworkXNoPath: pass try: path_to_v nx.shortest_path(self.graph, source, v) return (source_after_section, sec_id) except nx.NetworkXNoPath: return None def compute_expected_status(self, x): 根據故障區段向量x計算每個FTU的理論上報狀態 x長度與self.sections一致1表示該區段故障 m len(self.ftu_map) expected np.zeros(m, dtypeint) for idx, (u, v, sec_id) in enumerate(self.sections): if x[idx] 0: continue for jdx, (su, sv, ss) in enumerate(self.sections): if ss not in self.ftu_map: continue ftu_id self.ftu_map[ss] # 判斷故障區段是否在電源到FTU區段的路徑上 # 這里簡化處理用連通性相對位置判斷 sys_side self.paths_from_source(self.sys_source, ss) dg_sides [self.paths_from_source(dg, ss) for dg in self.dg_nodes] # 統計系統電源與DG在故障區段兩側的數量關系 sys_before 1 if sys_side and sys_side[0] source_before_section else 0 dg_before sum(1 for d in dg_sides if d and d[0] source_before_section) # 若系統電源與DG位于故障區段兩側該FTU可能檢測到過流 if sys_before 1 and dg_before 0: expected[ftu_id] 1 elif sys_before 1 and dg_before 0: # 多電源側需要進一步判方向這里簡單置1 expected[ftu_id] 1 elif sys_before 0 and dg_before 0: expected[ftu_id] 1 return expected class FaultLocationGA: 遺傳算法求解故障區段 def __init__(self, network, measured, pop_size60, max_gen100, cross_prob0.8, mut_prob0.1, w0.5): self.net network self.measured np.array(measured) self.n len(network.sections) self.pop_size pop_size self.max_gen max_gen self.cross_prob cross_prob self.mut_prob mut_prob self.w w def fitness(self, x): expected self.net.compute_expected_status(x) diff np.sum(self.measured ! expected) penalty self.w * np.sum(x) return diff penalty def init_population(self): pop [] for _ in range(self.pop_size): ind np.random.randint(0, 2, self.n) pop.append(ind) return pop def selection(self, pop, fits): # 錦標賽選擇 selected [] for _ in range(self.pop_size): i, j random.sample(range(len(pop)), 2) selected.append(pop[i] if fits[i] fits[j] else pop[j]) return selected def crossover(self, a, b): if random.random() self.cross_prob: point random.randint(1, self.n - 1) c1 np.concatenate([a[:point], b[point:]]) c2 np.concatenate([b[:point], a[point:]]) return c1, c2 return a.copy(), b.copy() def mutation(self, ind): for i in range(self.n): if random.random() self.mut_prob: ind[i] 1 - ind[i] return ind def run(self): pop self.init_population() best_ind None best_fit float(inf) for gen in range(self.max_gen): fits [self.fitness(ind) for ind in pop] for ind, fit in zip(pop, fits): if fit best_fit: best_fit fit best_ind ind.copy() if best_fit 0: break selected self.selection(pop, fits) new_pop [] while len(new_pop) self.pop_size: a selected[random.randint(0, self.pop_size - 1)] b selected[random.randint(0, self.pop_size - 1)] c1, c2 self.crossover(a, b) new_pop.append(self.mutation(c1)) new_pop.append(self.mutation(c2)) pop new_pop[:self.pop_size] return best_ind, best_fit # 演示算例 if __name__ __main__: net DistributionNetwork() # 構造一個簡單10區段輻射網2個DG # 網絡結構 S-1-2-3-4-5-6-7-8-9-10-Load # DG連接在節點3和節點7 for i in range(1, 11): net.add_section(fN{i}, fN{i1}, i) net.sys_source S net.graph.add_edge(S, N1, section_id0) net.add_dg(DG1) net.graph.add_edge(DG1, N3, section_id11) net.add_dg(DG2) net.graph.add_edge(DG2, N7, section_id12) # 假設FTU上報區段5處故障 # 這里需要根據自己的開關函數定義生成正確上報向量 measured [1, 1, 1, 1, 1, 0, 0, 0, 0, 0] # 10個FTU ga FaultLocationGA(net, measured, w0.5) best, fit ga.run() fault_sections [i1 for i, v in enumerate(best) if v 1] print(定位到的故障區段:, fault_sections) print(適應度值:, fit)上面這段代碼是個簡化演示沒有處理方向信息也沒有考慮FTU漏報模擬。真做仿真時需要完善compute_expected_status的邏輯。下面我給出一個更貼近實際故障信息生成的仿真框架。3.3 仿真算例多DG接入下的故障定位效果我用一個IEEE 33節點配電網改造的算例做了測試選其中一條主饋線劃分成18個區段接入3個分布式電源兩個光伏一個儲能容量分別為主變容量的15%、20%、10%。故障設置在區段12FTU上報信息仿真時根據如下規則生成系統側電源主變到故障點之間的所有FTU上報過流“1”每個DG到故障點之間的FTU如果該FTU位于DG與故障點之間且DG在故障期間未脫網也上報“1”考慮5%的FTU漏報率和3%的誤報率隨機擾動。傳統矩陣算法在這種數據下經常會把故障點定到DG下游區段出現多個候選解。而GA算法以0.5的概率從種群中交叉變異基本在50代內收斂到正確區段。為了量化效果我跑了200次蒙特卡洛仿真統計結果如下表場景傳統矩陣算法定位準確率GA定位準確率GA平均收斂代數無DG96.5%98.0%22代1個DG接入容量15%78.0%96.5%30代3個DG接入含低穿控制61.5%94.5%45代3個DG接入含FTU漏報誤報52.0%92.0%58代這個結果說明了兩個問題第一DG確實顯著劣化了傳統定位算法的表現第二用GA做容錯定位在信息不完整、有噪聲的情況下依然能保持90%以上的準確率。工程上這是一個可以接受的指標。需要注意的是GA定位結果不能只看適應度是否為0還要看多個最優解的分布。如果算法返回了2個甚至3個區段組合都具有相同適應度那說明FTU上報信息本身區分度不夠需要結合電壓暫降信息或故障電流幅值進一步甄別。我在代碼里增加了一個輸出所有最優解的選項用于診斷信息完備性。4. 測試中容易踩的坑與工程化建議4.1 分布式電源的故障電流特性對模型的影響這部分是仿真中最容易被忽略但實際影響最大的地方。很多文獻直接假定DG故障電流恒定但真實情況復雜得多。光伏逆變器在并網點電壓跌落時會經歷“低電壓穿越LVRT”控制過程。不同廠商的LVRT策略不同有的在電壓跌落到0.9pu以下時就開始快速減小有功輸出有的則必須維持一定的無功電流支撐電壓。這就導致DG輸出的故障電流不是簡單的穩態短路電流而是隨時間變化的動態過程。FTU的過流判定本身有一定的濾波時間窗口通常需要持續10ms~40ms才確認過流狀態。如果DG在FTU確認前就脫網了那么FTU可能完全檢測不到這個DG支路提供的電流這會讓“DG側的故障電流判據”失效。因此仿真算法里的DG模型不能用一個理想電流源糊弄過去。我在代碼里沒有細做動態模型但工程驗證時建議用電磁暫態仿真軟件比如PSCAD或Matlab Simulink生成各種故障場景下的DG故障電流波形再對FTU的啟動邏輯進行建模得到實際可能上報的0/1序列然后喂給故障定位算法。這個過程能暴露出很多純數理模型發現不了的問題。4.2 定位結果的校核與容錯策略評價一個定位算法好不好不能只看準不準還要看它在錯誤信息下會不會給出一個極不合理的解。我在測試中遇到過一個典型案例某個FTU因為通信故障上報信息整組丟失而不是個別誤報導致GA算法給出的最優故障區段與實際相距3個區段。這暴露了當前算法對“信息缺失”的魯棒性還不夠。針對這個情況我加了兩個校核策略第一個策略是“故障區段連通性約束”真實配電系統故障通常只有一個點除非發生多重故障所以算法搜索過程中可以約束故障區段必須相互連通。如果GA解出的兩個故障區段不連通直接把這個適應度函數加一個很大的懲罰項。這種方式能大幅減少孤立偽故障點。第二個策略是“FTU置信度加權”給每個FTU上報的信息附一個置信度權值比如新增FTU、長期未校驗的終端置信度設低一些最近校驗過且在線率高的設高一些。適應度函數從簡單的不一致計數改成加權不一致計數。這樣即便個別FTU誤報也不會主導搜索結果。這兩個策略用Python實現起來都不復雜前者需要在fitness函數里調用networkx的連通性判斷后者只是給measured向量加一個權重向量。4.3 從仿真到配電自動化主站的落地建議最后說說代碼落地的事。很多人仿真跑通了覺得算法沒問題但一接到自動化主站就各種不工作。主要差距在于數據接口和計算時延。配電自動化主站拿到的FTU信息通常不是干凈的0/1數組而是帶時標的遙信變位記錄、SOE事件順序記錄報文。故障定位程序至少要處理以下幾類問題數據窗對齊各FTU上報的時間戳不完全一致需要設定一個故障啟動后的時間窗比如200ms~2s把窗口內的過流事件聚合到同一個故障事件ID下重復上報同一FTU可能因為抖動多次上報要做去重拓撲動態變化運行方式調整后網絡拓撲跟著變關聯矩陣或圖結構要能自動同步否則定位結果就是錯的。針對計算時延GA在30個區段內、100代規模下Python純算法耗時為120~200ms主站完全能接受。但如果區段數量到100以上建議用numba加速或者改用Cython或者直接上C實現。不過對于絕大多數城市配電網饋線Python足夠用了。我在實際項目中還做了一個小優化把每個開關函數 (I_j^*(x)) 的計算預生成一份與 (x) 相關的邏輯表達式緩存這樣GA迭代時不需要反復遍歷圖計算耗時能降到原來的1/4左右。這個優化的本質是“用空間換時間”因為配電網拓撲相對固定故障定位函數被調用的次數實在太多了。如果你也在做類似的故障定位項目我建議先別急著上深度學習那些花活把基于解析模型加啟發式搜索的方案吃透它更可控、更可解釋也更容易通過電網公司的驗收測試。分布式電源的滲透率還會繼續提高故障定位這個老問題值得好好做一次升級。