增加 wn_inp_path 参数以支持多进程处理

This commit is contained in:
2026-03-07 15:34:40 +08:00
parent 05ca940c9f
commit a7e3b6aff9
3 changed files with 110 additions and 20 deletions
@@ -179,6 +179,7 @@ def run_burst_location(
located_pipe, elapsed_seconds, simulation_times, _, similarity_series = (
DN_search_multi_simple_add_flow_count_new(
wn=wn,
wn_inp_path=str(inp_path),
G0=G0,
all_node=all_node,
node_x=node_x,
@@ -23,6 +23,7 @@ from .similarity_calculator import (
def _ensure_signatures_for_centers(
wn,
wn_inp_path,
center_list, # 本轮要用到的中心(list[str])
pressure_leak_all,
flow_leak_all, # 全量缓存(可为空 DF
@@ -71,6 +72,7 @@ def _ensure_signatures_for_centers(
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:
@@ -296,6 +298,7 @@ def cal_SD(located_pipe, real_pipe, pipe_x, pipe_y):
def DN_search_multi_simple_add_flow_count_new(
wn,
wn_inp_path,
G0,
all_node,
node_x,
@@ -390,6 +393,7 @@ def DN_search_multi_simple_add_flow_count_new(
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,
@@ -440,6 +444,7 @@ def DN_search_multi_simple_add_flow_count_new(
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,
@@ -555,6 +560,7 @@ def DN_search_multi_simple_add_flow_count_new(
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,
+103 -20
View File
@@ -2,15 +2,55 @@
import math
import multiprocessing as mp
import os
import sys
import pandas as pd
import wntr
_PIPE2LEAKNODE = None
_SIGNATURE_WN = None
_SIGNATURE_LEAK_MAG = None
_SIGNATURE_SENSOR_NAME = None
_SIGNATURE_WORKER_DATA = {}
def _cleanup_temp_files(prefix):
for ext in [".inp", ".rpt", ".bin", ".out"]:
temp_file = prefix + ext
if os.path.exists(temp_file):
try:
os.remove(temp_file)
except OSError:
pass
def _make_temp_prefix(tag):
temp_dir = os.path.abspath(os.path.join("temp", "burst_location"))
os.makedirs(temp_dir, exist_ok=True)
safe_tag = str(tag).replace(os.sep, "_").replace(" ", "_")
return os.path.join(temp_dir, f"{safe_tag}_{os.getpid()}")
def _snapshot_hydraulic_options(wn):
options = wn.options
return {
"demand_model": options.hydraulic.demand_model,
"duration": float(options.time.duration),
"hydraulic_timestep": float(options.time.hydraulic_timestep),
"pattern_timestep": float(options.time.pattern_timestep),
"report_timestep": float(options.time.report_timestep),
"required_pressure": float(options.hydraulic.required_pressure),
"minimum_pressure": float(options.hydraulic.minimum_pressure),
}
def _apply_hydraulic_options(wn, option_values):
options = wn.options
options.hydraulic.demand_model = option_values["demand_model"]
options.time.duration = option_values["duration"]
options.time.hydraulic_timestep = option_values["hydraulic_timestep"]
options.time.pattern_timestep = option_values["pattern_timestep"]
options.time.report_timestep = option_values["report_timestep"]
options.hydraulic.required_pressure = option_values["required_pressure"]
options.hydraulic.minimum_pressure = option_values["minimum_pressure"]
def simple_add_leak(wn, leak_mag, leak_pipe):
@@ -209,7 +249,9 @@ def ensure_mid_node(wn, leak_pipe):
raise KeyError(f"Cannot ensure mid node for pipe '{leak_pipe}'.")
def leak_simulation_pipe_dd_multi_pf(wn, leak_mag, leak_pipe, sensor_name):
def leak_simulation_pipe_dd_multi_pf(
wn, leak_mag, leak_pipe, sensor_name, file_prefix=None
):
"""
优化版:
- 不再 remove/add link/node
@@ -233,7 +275,10 @@ def leak_simulation_pipe_dd_multi_pf(wn, leak_mag, leak_pipe, sensor_name):
# 仿真
sim = wntr.sim.EpanetSimulator(wn)
results = sim.run_sim()
if file_prefix is None:
results = sim.run_sim()
else:
results = sim.run_sim(file_prefix=file_prefix)
# 输出(保持列顺序)
pressure_output = results.node["pressure"].loc[:, sensor_name]
@@ -242,6 +287,8 @@ def leak_simulation_pipe_dd_multi_pf(wn, leak_mag, leak_pipe, sensor_name):
finally:
# 关闭泄漏:还原 base_value
ts_obj.base_value = orig_base
if file_prefix is not None:
_cleanup_temp_files(file_prefix)
def prepare_leak_infrastructure(wn, candidate_pipes):
@@ -391,7 +438,13 @@ def cal_sum_demand(demand):
def cal_signature_pipe_multi_pf(
wn, leak_mag, candidate_center, timestep_list, sensor_name, n_workers=1
wn,
leak_mag,
candidate_center,
timestep_list,
sensor_name,
n_workers=1,
wn_inp_path=None,
):
candidate_center_num = len(candidate_center)
pressure_leak = pd.DataFrame(
@@ -402,21 +455,41 @@ def cal_signature_pipe_multi_pf(
# columns=sensor_f_name)
pressure_leak = pressure_leak.sort_index()
# flow_leak = flow_leak.sort_index()
can_fork = "fork" in mp.get_all_start_methods()
if n_workers > 1 and candidate_center_num > 1 and can_fork:
_set_signature_worker_context(wn, leak_mag, sensor_name)
can_parallel = (
n_workers > 1
and candidate_center_num > 1
and wn_inp_path is not None
and len(str(wn_inp_path)) > 0
)
if can_parallel:
option_values = _snapshot_hydraulic_options(wn)
worker_count = min(n_workers, candidate_center_num)
with mp.get_context("fork").Pool(processes=worker_count) as pool:
start_methods = mp.get_all_start_methods()
context_name = "spawn" if "spawn" in start_methods else start_methods[0]
with mp.get_context(context_name).Pool(
processes=worker_count,
initializer=_signature_worker_init,
initargs=(
str(wn_inp_path),
float(leak_mag),
list(sensor_name),
option_values,
),
) as pool:
for i, (center_name, pressure_array) in enumerate(
pool.imap(_run_signature_for_center, candidate_center)
pool.imap(_signature_worker_run_center, candidate_center)
):
pressure_leak.loc[(center_name, slice(None)), :] = pressure_array
sys.stdout.write("\r" + "已经完成计算" + str(i + 1) + "个特征中心")
_set_signature_worker_context(None, None, None)
else:
for i in range(candidate_center_num):
temp_prefix = _make_temp_prefix(f"sig_{i}")
wn, pressure_output = leak_simulation_pipe_dd_multi_pf(
wn, leak_mag, candidate_center[i], sensor_name
wn,
leak_mag,
candidate_center[i],
sensor_name,
file_prefix=temp_prefix,
)
# leak_or_not_list.append(leak_or_not)
pressure_leak.loc[(candidate_center[i], slice(None)), :] = (
@@ -427,16 +500,26 @@ def cal_signature_pipe_multi_pf(
return pressure_leak, candidate_center
def _set_signature_worker_context(wn, leak_mag, sensor_name):
global _SIGNATURE_WN, _SIGNATURE_LEAK_MAG, _SIGNATURE_SENSOR_NAME
_SIGNATURE_WN = wn
_SIGNATURE_LEAK_MAG = leak_mag
_SIGNATURE_SENSOR_NAME = sensor_name
def _signature_worker_init(inp_path, leak_mag, sensor_name, option_values):
global _SIGNATURE_WORKER_DATA
wn = wntr.network.WaterNetworkModel(inp_path)
_apply_hydraulic_options(wn, option_values)
_SIGNATURE_WORKER_DATA = {
"wn": wn,
"leak_mag": leak_mag,
"sensor_name": sensor_name,
}
def _run_signature_for_center(center_name):
def _signature_worker_run_center(center_name):
data = _SIGNATURE_WORKER_DATA
temp_prefix = _make_temp_prefix(f"sig_worker_{center_name}")
_, pressure_output = leak_simulation_pipe_dd_multi_pf(
_SIGNATURE_WN, _SIGNATURE_LEAK_MAG, center_name, _SIGNATURE_SENSOR_NAME
data["wn"],
data["leak_mag"],
center_name,
data["sensor_name"],
file_prefix=temp_prefix,
)
return center_name, pressure_output.to_numpy()