"""爆管定位主模块。""" import copy import math import os import sys from datetime import datetime from time import perf_counter import networkx as nx import numpy as np import pandas as pd from .leak_simulator import cal_signature_pipe_multi_pf from .network_partitioner import ( cal_group_num, metis_grouping_pipe_weight, visualize_metis_partition, ) from .similarity_calculator import ( adjust_ratio, cal_similarity_all_multi_new_sq_improve_double_lzr, decode_mode, extra_judge, update_similarity, ) def _ensure_signatures_for_centers( wn, wn_inp_path, center_list, # 本轮要用到的中心(list[str]) pressure_leak_all, flow_leak_all, # 全量缓存(可为空 DF) timestep_list, # 你现有的时序列表 pressure_monitor, flow_monitor, # 用来推断传感器列名 leak_mag, n_workers=1, ): """ 只为缺失的中心补算 SLF(调用你现有的 cal_signature_pipe_multi_pf), 并把补算结果并回缓存。返回: pressure_leak_subset, flow_leak_subset, pressure_leak_all_new, flow_leak_all_new 其中 subset 只包含 center_list 的行(顺序与 center_list 保持一致)。 """ center_list = _dedupe_preserve_order(center_list) # 1) 推断传感器列名(与现有数据保持一致) sensor_name_all = list(pressure_monitor.columns) sensor_f_name_all = ( list(flow_monitor.columns) if (flow_monitor is not None and hasattr(flow_monitor, "columns")) else [] ) # 2) 取出缓存里已经有的中心(考虑 MultiIndex 的第 0 层为 pipe) def _existing_pipes(df): if df is None or len(df) == 0: return set() idx = df.index if isinstance(idx, pd.MultiIndex): return set(idx.get_level_values(0)) else: return set(idx) exist_p = _existing_pipes(pressure_leak_all) need = [p for p in center_list if p not in exist_p] # 3) 若有缺失中心,仅为这些中心补算一次 if len(need) > 0: p_new, _ = cal_signature_pipe_multi_pf( wn, leak_mag, need, timestep_list, sensor_name_all, n_workers=n_workers, wn_inp_path=wn_inp_path, ) # 初始化空缓存时,做一次“同构化” if pressure_leak_all is None or len(pressure_leak_all) == 0: pressure_leak_all = p_new else: pressure_leak_all = pd.concat([pressure_leak_all, p_new], axis=0) # if (flow_leak_all is None or len(flow_leak_all) == 0) and f_new is not None: # flow_leak_all = f_new # elif f_new is not None: # flow_leak_all = pd.concat([flow_leak_all, f_new], axis=0) # 去重(如果既有缓存里不小心有重复中心) if isinstance(pressure_leak_all.index, pd.MultiIndex): pressure_leak_all = pressure_leak_all[ ~pressure_leak_all.index.duplicated(keep="last") ] if flow_leak_all is not None and len(flow_leak_all) > 0: flow_leak_all = flow_leak_all[ ~flow_leak_all.index.duplicated(keep="last") ] else: pressure_leak_all = pressure_leak_all[ ~pressure_leak_all.index.duplicated(keep="last") ] if flow_leak_all is not None and len(flow_leak_all) > 0: flow_leak_all = flow_leak_all[ ~flow_leak_all.index.duplicated(keep="last") ] # 4) 从更新后的缓存里,取出这轮需要的中心子集(顺序与 center_list 一致) if isinstance(pressure_leak_all.index, pd.MultiIndex): pressure_subset = pressure_leak_all.loc[center_list] flow_subset = ( flow_leak_all.loc[center_list] if (flow_leak_all is not None and len(flow_leak_all) > 0) else None ) else: pressure_subset = pressure_leak_all.loc[center_list, :] flow_subset = ( flow_leak_all.loc[center_list, :] if (flow_leak_all is not None and len(flow_leak_all) > 0) else None ) return pressure_subset, flow_subset, pressure_leak_all, flow_leak_all def area_output_num_ki_improve( candidate_center, candidate_group, similarity, new_all_node, top_group_ratio, top_pipe_num_max, top_pipe_num_min, cut_ratio, ): final_area = [] final_center = [] all_node_iter = [] if similarity.index.is_unique == False: total_center_num = len(list(set(similarity.index))) else: total_center_num = len(similarity.index) next_group_num = min( total_center_num, math.ceil(total_center_num / cut_ratio * top_group_ratio) ) for i in range(next_group_num): top_center = similarity.index[i] top_center_index = find_list_repeat(candidate_center, top_center) for j in range(len(top_center_index)): final_area = final_area + candidate_group[top_center_index[j]] all_node_iter = all_node_iter + list(new_all_node[top_center_index[j]]) final_center.append(top_center) final_area = list(set(final_area)) if len(final_area) > top_pipe_num_max: if_end = 0 elif len(final_area) > top_pipe_num_min: if_end = 1 elif total_center_num == next_group_num: if_end = 1 else: if_end = 1 for i in np.arange(next_group_num, total_center_num, 1): before_list = copy.deepcopy(final_area) top_center = similarity.index[i] top_center_index = candidate_center.index(top_center) temp_group = final_area + candidate_group[top_center_index] temp_area = list(set(temp_group)) if len(temp_area) < top_pipe_num_min: final_center.append(top_center) all_node_iter = all_node_iter + list(new_all_node[top_center_index]) final_area = temp_area elif len(temp_area) < top_pipe_num_max: final_center.append(top_center) all_node_iter = all_node_iter + list(new_all_node[top_center_index]) final_area = temp_area break else: a = len(temp_area) - top_pipe_num_max b = top_pipe_num_min - len(before_list) if a >= b: final_area = before_list else: final_center.append(top_center) all_node_iter = all_node_iter + list(new_all_node[top_center_index]) final_area = temp_area break final_center = list(set(final_center)) all_node_iter = list(set(all_node_iter)) return final_area, final_center, all_node_iter, if_end def find_list_repeat(candidate_center, target): repeated_list = [] for index, nums in enumerate(candidate_center): if nums == target: repeated_list.append(index) return repeated_list def _dedupe_preserve_order(items): seen = set() output = [] for item in items: if item in seen: continue seen.add(item) output.append(item) return output def _accumulate_stage(stage_timing, stage_name, started_at): stage_timing[stage_name] = stage_timing.get(stage_name, 0.0) + ( perf_counter() - started_at ) def _write_last_round_candidates_csv( csv_path, exit_condition, iteration_count, similarity_mode, candidate_details, fallback_similarity, ): if not csv_path: return None timestamp_suffix = datetime.now().strftime("%Y%m%d_%H%M%S") base_path, ext = os.path.splitext(csv_path) ext = ext or ".csv" output_path = f"{base_path}_{timestamp_suffix}{ext}" if candidate_details is not None and len(candidate_details) > 0: export_df = candidate_details.copy() if export_df.index.name == "pipe_id": export_df = export_df.reset_index() else: export_df = pd.DataFrame( { "pipe_id": [str(pipe_id) for pipe_id in fallback_similarity.index], "final_similarity": [float(value) for value in fallback_similarity.values], } ) export_df["exit_condition"] = exit_condition export_df["iterations"] = int(iteration_count) export_df["similarity_mode"] = similarity_mode parent_dir = os.path.dirname(output_path) if parent_dir: os.makedirs(parent_dir, exist_ok=True) export_df.to_csv(output_path, index=False, encoding="utf-8-sig") return output_path def cal_DtoTop1( G0, pipe_leak, located_pipe, pipe_start_node_all, pipe_end_node_all, pipe_length ): if pipe_leak == located_pipe: result_DtoTop1 = 0 result_DtoTop1_num = 0 else: pipe_leak_start_node = pipe_start_node_all[pipe_leak] pipe_leak_end_node = pipe_end_node_all[pipe_leak] located_pipe_start_node = pipe_start_node_all[located_pipe] located_pipe_end_node = pipe_end_node_all[located_pipe] DtoTop1_series = pd.Series(dtype=object) DtoTop1_num_series = pd.Series(dtype=object) DtoTop1_series["ss"] = nx.shortest_path_length( G0, pipe_leak_start_node, located_pipe_start_node, weight="weight" ) DtoTop1_series["se"] = nx.shortest_path_length( G0, pipe_leak_start_node, located_pipe_end_node, weight="weight" ) DtoTop1_series["es"] = nx.shortest_path_length( G0, pipe_leak_end_node, located_pipe_start_node, weight="weight" ) DtoTop1_series["ee"] = nx.shortest_path_length( G0, pipe_leak_end_node, located_pipe_end_node, weight="weight" ) DtoTop1_num_series["ss"] = nx.shortest_path_length( G0, pipe_leak_start_node, located_pipe_start_node ) DtoTop1_num_series["se"] = nx.shortest_path_length( G0, pipe_leak_start_node, located_pipe_end_node ) DtoTop1_num_series["es"] = nx.shortest_path_length( G0, pipe_leak_end_node, located_pipe_start_node ) DtoTop1_num_series["ee"] = nx.shortest_path_length( G0, pipe_leak_end_node, located_pipe_end_node ) if DtoTop1_num_series.min() == 0: result_DtoTop1_num = 1 result_DtoTop1 = DtoTop1_series.max() / 2 else: result_DtoTop1_num = DtoTop1_num_series.min() + 1 DtoTop1_type = DtoTop1_series.argmin() result_DtoTop1 = ( DtoTop1_series[DtoTop1_type] + (pipe_length[pipe_leak] + pipe_length[located_pipe]) / 2 ) return result_DtoTop1, result_DtoTop1_num def cal_RR(located_pipe, similarity_sp): if located_pipe in similarity_sp.index: rank = similarity_sp.index.get_loc(located_pipe) RR = rank / len(similarity_sp.index) else: RR = 1.1 return RR def cal_cover(similarity, leak_pipe): if leak_pipe in list(similarity.index): cover = 1 else: cover = 0 return cover def cal_SD(located_pipe, real_pipe, pipe_x, pipe_y): dx = pipe_x[located_pipe] - pipe_x[real_pipe] dy = pipe_y[located_pipe] - pipe_y[real_pipe] SD = math.sqrt(dx * dx + dy * dy) return SD def DN_search_multi_simple_add_flow_count_new( wn, wn_inp_path, G0, all_node, node_x, node_y, pipe_start_node_all, pipe_end_node_all, couple_node_length, node_pipe_dic, all_node_series, top_group_ratio, top_pipe_num_max, top_pipe_num_min, candidate_pipe_input_initial, similarity_mode, pressure_monitor, pressure_predict, pressure_normal, pressure_leak_all, flow_monitor, flow_predict, flow_normal, flow_leak_all, timestep_list, max_flow, group_basic_num, Top_sensor_num, if_gy, pressure_threshold, leak_mag, n_workers=1, stage_timing=None, partition_on_full_graph=True, visualize_partition=False, visualize_pause_seconds=0.3, final_candidates_csv_path=None, ): if stage_timing is None: stage_timing = {} exit_condition = "unknown" final_candidates_csv = None iter_count = 0 all_node_iter = copy.deepcopy(all_node) candidate_pipe_input = copy.deepcopy(candidate_pipe_input_initial) # 可能漏损管段 t1 = datetime.now() if_flow, if_only_cos, if_only_flow = decode_mode(similarity_mode) # 定位方法 # threshold if if_only_flow == 1: dpressure = (flow_predict - flow_monitor).mean() dpressure = dpressure.abs() effective_sensor = list(dpressure.index) else: dpressure = (pressure_predict - pressure_monitor).mean() dpressure = dpressure.abs() dpressure = dpressure[dpressure > pressure_threshold] effective_sensor = list(dpressure.index) simulation_times = 0 # 模拟次数 if len(dpressure) > 0: break_flag = 0 last_round_candidate_details = None cos_h = 0 dis_h = 0 dis_f_h = 0 if_compalsive = 0 record_center_dataset = [] record_center_set = set() # iter while 1: final_area = [] final_center = [] group_num = cal_group_num(candidate_pipe_input, group_basic_num) partition_nodes = all_node if partition_on_full_graph else all_node_iter # group 分组,得出候选漏损中心 stage_start = perf_counter() candidate_center_list, candidate_group_list, new_all_node = ( metis_grouping_pipe_weight( G0, wn, partition_nodes, candidate_pipe_input, group_num, node_x, node_y, pipe_start_node_all, pipe_end_node_all, node_pipe_dic, all_node_series, couple_node_length, ) ) _accumulate_stage(stage_timing, "group_partitioning", stage_start) if visualize_partition: visualize_metis_partition( G0, candidate_center_list, candidate_group_list, node_x, node_y, pipe_start_node_all, pipe_end_node_all, title=( f"METIS Partition Iteration {iter_count + 1} | " f"candidate pipes={len(candidate_pipe_input)} " f"groups={len(candidate_group_list)}" ), block=False, pause_seconds=visualize_pause_seconds, ) simulation_times = simulation_times + len(candidate_center_list) # pick_pressure_leak # pressure_leak = pressure_leak_all.loc[candidate_center_list].loc[:, :] # flow_leak = flow_leak_all.loc[candidate_center_list].loc[:, :] # —— 新增泄漏量(保持你现在的一致,或从外部传入)—— # —— 只为缺失中心补算,然后取本轮需要的中心子集 —— stage_start = perf_counter() pressure_leak, flow_leak, pressure_leak_all, flow_leak_all = ( _ensure_signatures_for_centers( wn=wn, wn_inp_path=wn_inp_path, center_list=candidate_center_list, pressure_leak_all=pressure_leak_all, flow_leak_all=flow_leak_all, timestep_list=timestep_list, pressure_monitor=pressure_monitor, flow_monitor=flow_monitor, leak_mag=leak_mag, n_workers=n_workers, ) ) _accumulate_stage(stage_timing, "signature_for_candidates", stage_start) # pressure_leak_f= pressure_leak.swaplevel() # -------------------------------------------------------- add_center = [] leak_center_dict = dict() for i in range(len(candidate_center_list)): houxuan_center = [] candidate_group_set = set(candidate_group_list[i]) for each_center in record_center_dataset: if ( each_center in candidate_group_set and each_center != candidate_center_list[i] ): houxuan_center.append(each_center) add_center = add_center + houxuan_center houxuan_center.append(candidate_center_list[i]) leak_center_dict[candidate_center_list[i]] = houxuan_center add_center = _dedupe_preserve_order(add_center) for each_center in candidate_center_list: if each_center not in record_center_set: record_center_dataset.append(each_center) record_center_set.add(each_center) # -------------------------------------------------------- # -------------------------------------------------------- # if len(add_center) > 0: # s3 = pressure_leak_all.loc[add_center] # pressure_leak = pd.concat([pressure_leak, s3]) # s4 = flow_leak_all.loc[add_center] # flow_leak = pd.concat([flow_leak, s4]) # -------------------------------------------------------- # 只为 add_center 里还没算过的中心补算,并与本轮中心合并 if len(add_center) > 0: stage_start = perf_counter() pressure_add, flow_add, pressure_leak_all, flow_leak_all = ( _ensure_signatures_for_centers( wn=wn, wn_inp_path=wn_inp_path, center_list=add_center, pressure_leak_all=pressure_leak_all, flow_leak_all=flow_leak_all, timestep_list=timestep_list, pressure_monitor=pressure_monitor, flow_monitor=flow_monitor, leak_mag=leak_mag, # 与上面一致 n_workers=n_workers, ) ) _accumulate_stage( stage_timing, "signature_for_extra_centers", stage_start ) pressure_leak = pd.concat([pressure_leak, pressure_add], axis=0) if (flow_leak is not None) and (flow_add is not None): flow_leak = pd.concat([flow_leak, flow_add], axis=0) # -------------------------------------------------------- # if len(candidate_pipe_input) < 1.2 * top_pipe_num_max / top_group_ratio: if_compalsive = 1 cos_h, dis_h, dis_f_h = adjust_ratio(similarity_mode, cos_h, dis_h, dis_f_h) candidate_center_list_sup = _dedupe_preserve_order( candidate_center_list + add_center ) stage_start = perf_counter() similarity, cos_h, dis_h, dis_f_h, break_flag, similarity_details = ( cal_similarity_all_multi_new_sq_improve_double_lzr( candidate_center_list_sup, similarity_mode, pressure_leak, pressure_monitor, pressure_predict, pressure_normal, if_flow, if_only_cos, if_only_flow, flow_leak, flow_monitor, flow_predict, flow_normal, timestep_list, Top_sensor_num, if_gy, effective_sensor, cos_h, dis_h, dis_f_h, if_compalsive, max_flow, ) ) last_round_candidate_details = similarity_details _accumulate_stage(stage_timing, "similarity_ranking", stage_start) if break_flag == 1: exit_condition = "similarity_break_flag" break new_similarity = update_similarity( candidate_center_list, similarity, leak_center_dict ) if len(candidate_pipe_input) > top_pipe_num_max / top_group_ratio: cut_ratio, new_similarity = extra_judge(new_similarity) else: cut_ratio = 1 stage_start = perf_counter() final_area_t, final_center_t, all_node_new_1, if_end = ( area_output_num_ki_improve( candidate_center_list, candidate_group_list, new_similarity, new_all_node, top_group_ratio, top_pipe_num_max, top_pipe_num_min, cut_ratio, ) ) _accumulate_stage(stage_timing, "candidate_area_selection", stage_start) final_area = final_area + final_area_t final_center = final_center + final_center_t final_area = list(set(final_area)) final_center = list(set(final_center)) if if_end == 1: exit_condition = "candidate_area_if_end" break elif len(candidate_pipe_input) == len(final_area): exit_condition = "candidate_size_no_change" break else: candidate_pipe_input = final_area if not partition_on_full_graph: all_node_iter = all_node_new_1 iter_count += 1 sys.stdout.write( "\r" + "已经完成" + str(iter_count) + "次迭代计算" + "候选节点" + str(len(final_area)) + "个" ) # if break_flag == 0: # final_area_pipe = copy.deepcopy(final_area) # simulation_times = simulation_times + len(final_area) # pressure_leak_sp = pressure_leak_all.loc[final_area_pipe].loc[:, :] # flow_leak_sp = flow_leak_all.loc[final_area_pipe].loc[:, :] # similarity_sp, cos_h, dis_h, dis_f_h, break_flag = cal_similarity_all_multi_new_sq_improve_double_lzr( # final_area_pipe, similarity_mode, pressure_leak_sp, # pressure_monitor, pressure_predict, pressure_normal, if_flow, # if_only_cos, if_only_flow, # flow_leak_sp, flow_monitor, flow_predict, flow_normal, # timestep_list, Top_sensor_num, if_gy, effective_sensor, cos_h, dis_h, dis_f_h, if_compalsive, max_flow) if break_flag == 0: final_area_pipe = list(final_area) # 确保是 list # 只为还没算过的管段补齐 SLF(按需计算) stage_start = perf_counter() pressure_leak_sp, flow_leak_sp, pressure_leak_all, flow_leak_all = ( _ensure_signatures_for_centers( wn=wn, wn_inp_path=wn_inp_path, center_list=final_area_pipe, # 这次要用的“最终区域里的所有管段” pressure_leak_all=pressure_leak_all, # 累积缓存(会被更新) flow_leak_all=flow_leak_all, timestep_list=timestep_list, pressure_monitor=pressure_monitor, flow_monitor=flow_monitor, leak_mag=leak_mag, n_workers=n_workers, ) ) _accumulate_stage(stage_timing, "signature_for_final_area", stage_start) # 如果你要精确统计模拟次数,这里可以加上“本次新补的数量”, # 做法:让 _ensure_signatures_for_centers 额外返回 need_cnt,再 simulation_times += need_cnt stage_start = perf_counter() ( similarity_sp, cos_h, dis_h, dis_f_h, break_flag, similarity_details, ) = ( cal_similarity_all_multi_new_sq_improve_double_lzr( final_area_pipe, similarity_mode, pressure_leak_sp, pressure_monitor, pressure_predict, pressure_normal, if_flow, if_only_cos, if_only_flow, flow_leak_sp, flow_monitor, flow_predict, flow_normal, timestep_list, Top_sensor_num, if_gy, effective_sensor, cos_h, dis_h, dis_f_h, if_compalsive, max_flow, ) ) last_round_candidate_details = similarity_details _accumulate_stage(stage_timing, "similarity_final", stage_start) else: dpressure = (pressure_predict - pressure_monitor).mean() dpressure = dpressure.abs() simulation_times = simulation_times + len(dpressure.index) similarity_sp = pd.Series(dtype=object) for each_node in dpressure.index: pipe = node_pipe_dic[each_node][0] similarity_sp.loc[pipe] = dpressure.loc[each_node] similarity_sp = similarity_sp.sort_values(ascending=False) t2 = datetime.now() final_area_pipe = [] sys.stdout.write( "\r" + "已经完成" + str(iter_count + 1) + "次迭代计算" + "候选节点" + str(len(final_area_pipe)) + "个" ) t2 = datetime.now() dt = (t2 - t1).seconds final_candidates_csv = _write_last_round_candidates_csv( csv_path=final_candidates_csv_path, exit_condition=exit_condition, iteration_count=iter_count + 1, similarity_mode=similarity_mode, candidate_details=last_round_candidate_details, fallback_similarity=similarity_sp, ) else: exit_condition = "no_effective_sensor_after_threshold" dpressure = (pressure_predict - pressure_monitor).mean() dpressure = dpressure.abs() similarity_sp = pd.Series(dtype=object) for each_node in dpressure.index: pipe = node_pipe_dic[each_node][0] similarity_sp.loc[pipe] = dpressure.loc[each_node] similarity_sp = similarity_sp.sort_values(ascending=False) t2 = datetime.now() dt = (t2 - t1).seconds final_candidates_csv = _write_last_round_candidates_csv( csv_path=final_candidates_csv_path, exit_condition=exit_condition, iteration_count=0, similarity_mode=similarity_mode, candidate_details=None, fallback_similarity=similarity_sp, ) stage_timing["iterations"] = iter_count + 1 if len(dpressure) > 0 else 0 stage_timing["total_elapsed_seconds"] = float(dt) stage_timing["exit_condition"] = exit_condition stage_timing["final_candidates_csv"] = final_candidates_csv return ( similarity_sp.index[0], dt, simulation_times, wn, similarity_sp, exit_condition, final_candidates_csv, ) class BurstLocator: @staticmethod def DN_search_multi_simple_add_flow_count_new(*args, **kwargs): return DN_search_multi_simple_add_flow_count_new(*args, **kwargs) @staticmethod def area_output_num_ki_improve(*args, **kwargs): return area_output_num_ki_improve(*args, **kwargs) @staticmethod def cal_DtoTop1(*args, **kwargs): return cal_DtoTop1(*args, **kwargs) @staticmethod def cal_RR(*args, **kwargs): return cal_RR(*args, **kwargs) @staticmethod def cal_cover(*args, **kwargs): return cal_cover(*args, **kwargs) @staticmethod def cal_SD(*args, **kwargs): return cal_SD(*args, **kwargs)