564 lines
19 KiB
Python
564 lines
19 KiB
Python
"""漏损模拟模块。"""
|
||
|
||
import math
|
||
import multiprocessing as mp
|
||
import os
|
||
import sys
|
||
|
||
import pandas as pd
|
||
import wntr
|
||
|
||
from app.algorithms._utils import _cleanup_temp_files
|
||
|
||
_PIPE2LEAKNODE = None
|
||
_SIGNATURE_WORKER_DATA = {}
|
||
|
||
|
||
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):
|
||
whole_inf = dict()
|
||
leak_pipe_self = wn.get_link(leak_pipe)
|
||
pipe_diameter = leak_pipe_self.diameter
|
||
pipe_length = leak_pipe_self.length
|
||
pipe_roughness = leak_pipe_self.roughness
|
||
pipe_minor_loss = leak_pipe_self.minor_loss
|
||
# pipe_status = leak_pipe_self.status
|
||
# pipe_check_valve = leak_pipe_self.check_valve
|
||
pipe_start_node = leak_pipe_self.start_node_name
|
||
pipe_end_node = leak_pipe_self.end_node_name
|
||
|
||
# close the pipe
|
||
# leak_pipe_self.status = 'Closed'
|
||
wn.remove_link(leak_pipe)
|
||
# add the pipe
|
||
add_pipe1 = leak_pipe + "A"
|
||
add_pipe2 = leak_pipe + "B"
|
||
add_node = leak_pipe + "_"
|
||
|
||
start_n = wn.get_node(pipe_start_node)
|
||
end_n = wn.get_node(pipe_end_node)
|
||
if start_n.node_type == "Reservoir":
|
||
end_n_elevation = end_n.elevation
|
||
start_n_elevation = end_n_elevation
|
||
elif end_n.node_type == "Reservoir":
|
||
start_n_elevation = start_n.elevation
|
||
end_n_elevation = start_n_elevation
|
||
else:
|
||
end_n_elevation = end_n.elevation
|
||
start_n_elevation = start_n.elevation
|
||
elevation_self = (start_n_elevation + end_n_elevation) / 2
|
||
coordinates_self = (
|
||
(start_n.coordinates[0] + end_n.coordinates[0]) / 2,
|
||
(start_n.coordinates[1] + end_n.coordinates[1]),
|
||
)
|
||
|
||
wn.add_junction(
|
||
add_node, base_demand=0, elevation=elevation_self, coordinates=coordinates_self
|
||
)
|
||
leak_node = wn.get_node(add_node)
|
||
wn.add_pipe(
|
||
add_pipe1,
|
||
start_node_name=pipe_start_node,
|
||
end_node_name=add_node,
|
||
length=pipe_length / 2,
|
||
diameter=pipe_diameter,
|
||
roughness=pipe_roughness,
|
||
minor_loss=pipe_minor_loss,
|
||
)
|
||
wn.add_pipe(
|
||
add_pipe2,
|
||
start_node_name=pipe_end_node,
|
||
end_node_name=add_node,
|
||
length=pipe_length / 2,
|
||
diameter=pipe_diameter,
|
||
roughness=pipe_roughness,
|
||
minor_loss=pipe_minor_loss,
|
||
)
|
||
|
||
# simulation
|
||
leak_node.add_demand(base=leak_mag, pattern_name="add_leak")
|
||
|
||
whole_inf["leak_node_name"] = add_node
|
||
whole_inf["add_pipe1"] = add_pipe1
|
||
whole_inf["add_pipe2"] = add_pipe2
|
||
whole_inf["leak_pipe"] = leak_pipe
|
||
whole_inf["pipe_start_node"] = pipe_start_node
|
||
whole_inf["pipe_end_node"] = pipe_end_node
|
||
whole_inf["pipe_length"] = pipe_length
|
||
whole_inf["pipe_diameter"] = pipe_diameter
|
||
whole_inf["pipe_roughness"] = pipe_roughness
|
||
whole_inf["pipe_minor_loss"] = pipe_diameter
|
||
return wn, whole_inf, add_pipe1
|
||
|
||
|
||
def simple_recover_wn(wn, whole_inf):
|
||
leak_node = wn.get_node(whole_inf["leak_node_name"])
|
||
|
||
del leak_node.demand_timeseries_list[-1]
|
||
# update
|
||
wn.remove_link(whole_inf["add_pipe1"])
|
||
wn.remove_link(whole_inf["add_pipe2"])
|
||
wn.remove_node(whole_inf["leak_node_name"])
|
||
# open the pipe
|
||
# leak_pipe_self.status = 'Open'
|
||
wn.add_pipe(
|
||
whole_inf["leak_pipe"],
|
||
start_node_name=whole_inf["pipe_start_node"],
|
||
end_node_name=whole_inf["pipe_end_node"],
|
||
length=whole_inf["pipe_length"],
|
||
diameter=whole_inf["pipe_diameter"],
|
||
roughness=whole_inf["pipe_roughness"],
|
||
minor_loss=whole_inf["pipe_minor_loss"],
|
||
)
|
||
|
||
return wn
|
||
|
||
|
||
def disable_all_controls_temporarily(wn):
|
||
"""返回(控制名, 控制对象)的列表,之后可用 restore_controls 还原。"""
|
||
removed = []
|
||
# WNTR 的控制都在 wn.control_name_list / wn.get_control / wn.remove_control
|
||
for cname in list(wn.control_name_list):
|
||
ctrl = wn.get_control(cname)
|
||
removed.append((cname, ctrl))
|
||
wn.remove_control(cname)
|
||
return removed
|
||
|
||
|
||
def restore_controls(wn, removed):
|
||
"""把先前禁用的控制全部加回去。"""
|
||
for cname, ctrl in removed:
|
||
wn.add_control(cname, ctrl)
|
||
|
||
|
||
def set_pipe2leaknode_mapping(mapping):
|
||
global _PIPE2LEAKNODE
|
||
_PIPE2LEAKNODE = mapping
|
||
|
||
|
||
def _get_or_create_leak_demand_ts(leak_node):
|
||
"""
|
||
返回:泄漏专用 demand 的下标 idx。
|
||
若不存在,以 category='leak' 新建一条 base=0.0 的 demand。
|
||
"""
|
||
# 先尝试找到已有的 'leak' 分类
|
||
for i, ts in enumerate(leak_node.demand_timeseries_list):
|
||
# WNTR 的 Demand object 存在 category 属性
|
||
if getattr(ts, "category", None) == "leak":
|
||
return i
|
||
# 没有则新建(base=0.0,后续临时改 base_value)
|
||
leak_node.add_demand(base=0.0, pattern_name=None, category="leak")
|
||
return len(leak_node.demand_timeseries_list) - 1
|
||
|
||
|
||
def ensure_mid_node(wn, leak_pipe):
|
||
add_pipe1 = f"{leak_pipe}A"
|
||
add_pipe2 = f"{leak_pipe}B"
|
||
add_node = f"{leak_pipe}__mid"
|
||
|
||
if add_node in wn.node_name_list:
|
||
return add_node
|
||
|
||
if leak_pipe in wn.link_name_list:
|
||
leak_pipe_self = wn.get_link(leak_pipe)
|
||
pipe_diameter = leak_pipe_self.diameter
|
||
pipe_length = leak_pipe_self.length
|
||
pipe_roughness = leak_pipe_self.roughness
|
||
pipe_minor_loss = leak_pipe_self.minor_loss
|
||
pipe_start_node = leak_pipe_self.start_node_name
|
||
pipe_end_node = leak_pipe_self.end_node_name
|
||
|
||
start_n = wn.get_node(pipe_start_node)
|
||
end_n = wn.get_node(pipe_end_node)
|
||
if start_n.node_type == "Reservoir":
|
||
end_elev = end_n.elevation
|
||
start_elev = end_elev
|
||
elif end_n.node_type == "Reservoir":
|
||
start_elev = start_n.elevation
|
||
end_elev = start_elev
|
||
else:
|
||
end_elev = end_n.elevation
|
||
start_elev = start_n.elevation
|
||
elev_mid = (start_elev + end_elev) / 2.0
|
||
x_mid = (start_n.coordinates[0] + end_n.coordinates[0]) / 2.0
|
||
y_mid = (start_n.coordinates[1] + end_n.coordinates[1]) / 2.0
|
||
|
||
wn.remove_link(leak_pipe)
|
||
wn.add_junction(
|
||
add_node, base_demand=0.0, elevation=elev_mid, coordinates=(x_mid, y_mid)
|
||
)
|
||
wn.add_pipe(
|
||
add_pipe1,
|
||
start_node_name=pipe_start_node,
|
||
end_node_name=add_node,
|
||
length=pipe_length / 2.0,
|
||
diameter=pipe_diameter,
|
||
roughness=pipe_roughness,
|
||
minor_loss=pipe_minor_loss,
|
||
)
|
||
wn.add_pipe(
|
||
add_pipe2,
|
||
start_node_name=add_node,
|
||
end_node_name=pipe_end_node,
|
||
length=pipe_length / 2.0,
|
||
diameter=pipe_diameter,
|
||
roughness=pipe_roughness,
|
||
minor_loss=pipe_minor_loss,
|
||
)
|
||
return add_node
|
||
|
||
# 若 A/B 已存在但中点不在,建议确认网络一致性
|
||
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, file_prefix=None
|
||
):
|
||
"""
|
||
优化版:
|
||
- 不再 remove/add link/node
|
||
- 直接在预插入的中点泄漏节点上设置 base_demand = leak_mag;仿真后设回 0
|
||
"""
|
||
wn.options.hydraulic.demand_model = "DD"
|
||
|
||
# 确保中点节点存在
|
||
leak_node_name = ensure_mid_node(wn, leak_pipe)
|
||
leak_node = wn.get_node(leak_node_name)
|
||
|
||
# 拿到泄漏专用的 demand time-series 下标
|
||
leak_idx = _get_or_create_leak_demand_ts(leak_node)
|
||
ts_obj = leak_node.demand_timeseries_list[leak_idx]
|
||
|
||
# 记录原值(通常是 0.0)
|
||
orig_base = ts_obj.base_value
|
||
try:
|
||
# 打开泄漏:只改 base_value,不碰 base_demand(只读)
|
||
ts_obj.base_value = float(leak_mag)
|
||
|
||
# 仿真
|
||
sim = wntr.sim.EpanetSimulator(wn)
|
||
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]
|
||
# flow_output = results.link['flowrate'].loc[:, sensor_f_name]
|
||
return wn, pressure_output
|
||
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):
|
||
"""
|
||
把 candidate_pipes 每条管段切成两段,并在中点插入一个泄漏节点(base_demand=0)。
|
||
返回一个映射:pipe_id -> leak_node_name
|
||
注意:只做一次;后续仿真通过在该节点设置 base_demand 实现“打开泄漏”,结束后恢复为 0。
|
||
"""
|
||
pipe2leaknode = {}
|
||
for leak_pipe in candidate_pipes:
|
||
if leak_pipe in pipe2leaknode:
|
||
continue
|
||
|
||
leak_pipe_self = wn.get_link(leak_pipe)
|
||
pipe_diameter = leak_pipe_self.diameter
|
||
pipe_length = leak_pipe_self.length
|
||
pipe_roughness = leak_pipe_self.roughness
|
||
pipe_minor_loss = leak_pipe_self.minor_loss
|
||
pipe_start_node = leak_pipe_self.start_node_name
|
||
pipe_end_node = leak_pipe_self.end_node_name
|
||
|
||
# 计算中点高程/坐标(与原逻辑一致)
|
||
start_n = wn.get_node(pipe_start_node)
|
||
end_n = wn.get_node(pipe_end_node)
|
||
if start_n.node_type == "Reservoir":
|
||
end_elev = end_n.elevation
|
||
start_elev = end_elev
|
||
elif end_n.node_type == "Reservoir":
|
||
start_elev = start_n.elevation
|
||
end_elev = start_elev
|
||
else:
|
||
end_elev = end_n.elevation
|
||
start_elev = start_n.elevation
|
||
elev_mid = (start_elev + end_elev) / 2.0
|
||
x_mid = (start_n.coordinates[0] + end_n.coordinates[0]) / 2.0
|
||
y_mid = (start_n.coordinates[1] + end_n.coordinates[1]) / 2.0
|
||
|
||
# 先删原管,再加中点与两段半长管(只做一次)
|
||
wn.remove_link(leak_pipe)
|
||
|
||
add_pipe1 = f"{leak_pipe}A"
|
||
add_pipe2 = f"{leak_pipe}B"
|
||
add_node = f"{leak_pipe}__mid" # 唯一命名,后面直接用它当泄漏节点
|
||
|
||
wn.add_junction(
|
||
add_node, base_demand=0.0, elevation=elev_mid, coordinates=(x_mid, y_mid)
|
||
)
|
||
wn.add_pipe(
|
||
add_pipe1,
|
||
start_node_name=pipe_start_node,
|
||
end_node_name=add_node,
|
||
length=pipe_length / 2.0,
|
||
diameter=pipe_diameter,
|
||
roughness=pipe_roughness,
|
||
minor_loss=pipe_minor_loss,
|
||
)
|
||
wn.add_pipe(
|
||
add_pipe2,
|
||
start_node_name=add_node,
|
||
end_node_name=pipe_end_node,
|
||
length=pipe_length / 2.0,
|
||
diameter=pipe_diameter,
|
||
roughness=pipe_roughness,
|
||
minor_loss=pipe_minor_loss,
|
||
)
|
||
|
||
pipe2leaknode[leak_pipe] = add_node
|
||
|
||
return pipe2leaknode
|
||
|
||
|
||
def normal_simulation_pf(
|
||
wn, drive_mode, sensor_name, sensor_f_name, inp_time, require_p, minimum_p
|
||
):
|
||
# inp_time = 0
|
||
if drive_mode == "PDD": # 需水量根据节点压力动态调整
|
||
wn.options.hydraulic.demand_model = "PDD"
|
||
wn.options.hydraulic.required_pressure = require_p
|
||
wn.options.hydraulic.minimum_pressure = minimum_p
|
||
elif drive_mode == "DD": # 需水量固定,与压力无关
|
||
wn.options.hydraulic.demand_model = "DD"
|
||
sim = wntr.sim.EpanetSimulator(wn)
|
||
results = sim.run_sim()
|
||
pressure_all = results.node["pressure"][sensor_name]
|
||
pressure = pressure_all.iloc[inp_time]
|
||
demand_all = results.node["demand"]
|
||
demand = demand_all.iloc[inp_time]
|
||
sum_demand = cal_sum_demand(demand)
|
||
flow_all = results.link["flowrate"][sensor_f_name]
|
||
flow = flow_all.iloc[inp_time]
|
||
|
||
top_sensor = pressure.idxmin()
|
||
basic_p = results.node["pressure"]
|
||
basic_p = basic_p.iloc[inp_time]
|
||
return pressure, flow, basic_p, top_sensor, sum_demand
|
||
|
||
|
||
def normal_simulation_multi_pf(
|
||
wn, drive_mode, sensor_name, sensor_f_name, require_p, minimum_p
|
||
):
|
||
# inp_time = 0
|
||
if drive_mode == "PDD":
|
||
wn.options.hydraulic.demand_model = "PDD"
|
||
wn.options.hydraulic.required_pressure = require_p
|
||
wn.options.hydraulic.minimum_pressure = minimum_p
|
||
elif drive_mode == "DD":
|
||
wn.options.hydraulic.demand_model = "DD"
|
||
sim = wntr.sim.EpanetSimulator(wn)
|
||
results = sim.run_sim()
|
||
pressure_all = results.node["pressure"][sensor_name]
|
||
pressure = pressure_all
|
||
demand_all = results.node["demand"]
|
||
demand = demand_all
|
||
flow = results.link["flowrate"][sensor_f_name]
|
||
sum_demand = pd.Series(dtype=object)
|
||
for i in range(len(demand.index)):
|
||
sum_demand[str(demand.index[i])] = cal_sum_demand(demand.iloc[i])
|
||
if type(pressure) == pd.core.series.Series:
|
||
top_sensor = pressure.idxmin()
|
||
else:
|
||
mean_pressure = pressure.mean()
|
||
top_sensor = mean_pressure.idxmin()
|
||
basic_p = results.node["pressure"]
|
||
return pressure, flow, basic_p, top_sensor, sum_demand
|
||
|
||
|
||
def simple_simulation_pf(wn, sensor_name, sensor_f_name, leak_pipe, add_pipe1):
|
||
sim = wntr.sim.EpanetSimulator(wn)
|
||
results = sim.run_sim()
|
||
pressure_all = results.node["pressure"][sensor_name]
|
||
if len(leak_pipe) > 0 and leak_pipe in sensor_f_name:
|
||
|
||
f_sensor_name = [add_pipe1 if i == leak_pipe else i for i in sensor_f_name]
|
||
flow_all = results.link["flowrate"][f_sensor_name]
|
||
flow_all.columns = sensor_f_name
|
||
else:
|
||
flow_all = results.link["flowrate"][sensor_f_name]
|
||
return pressure_all, flow_all
|
||
|
||
|
||
def cal_sum_demand(demand):
|
||
sum_demand = 0
|
||
for i in range(len(demand)):
|
||
if demand.iloc[i] > 0:
|
||
sum_demand += demand.iloc[i]
|
||
return sum_demand
|
||
|
||
|
||
def cal_signature_pipe_multi_pf(
|
||
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(
|
||
index=pd.MultiIndex.from_product([candidate_center, timestep_list]),
|
||
columns=sensor_name,
|
||
)
|
||
# flow_leak = pd.DataFrame(index=pd.MultiIndex.from_product([candidate_center, timestep_list]),
|
||
# columns=sensor_f_name)
|
||
pressure_leak = pressure_leak.sort_index()
|
||
# flow_leak = flow_leak.sort_index()
|
||
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)
|
||
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,
|
||
list(candidate_center),
|
||
),
|
||
) as pool:
|
||
for i, (center_name, pressure_array) in enumerate(
|
||
pool.imap(_signature_worker_run_center, candidate_center)
|
||
):
|
||
pressure_leak.loc[(center_name, slice(None)), :] = pressure_array
|
||
sys.stdout.write("\r" + "已经完成计算" + str(i + 1) + "个特征中心")
|
||
else:
|
||
# Pre-insert all mid-nodes so every simulation sees the same topology
|
||
for center in candidate_center:
|
||
ensure_mid_node(wn, center)
|
||
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,
|
||
file_prefix=temp_prefix,
|
||
)
|
||
# leak_or_not_list.append(leak_or_not)
|
||
pressure_leak.loc[(candidate_center[i], slice(None)), :] = (
|
||
pressure_output.to_numpy()
|
||
)
|
||
# flow_leak.loc[candidate_center[i]].loc[:, :] = flow_output
|
||
sys.stdout.write("\r" + "已经完成计算" + str(i + 1) + "个特征中心")
|
||
return pressure_leak, candidate_center
|
||
|
||
|
||
def _signature_worker_init(
|
||
inp_path, leak_mag, sensor_name, option_values, candidate_centers=None
|
||
):
|
||
global _SIGNATURE_WORKER_DATA
|
||
wn = wntr.network.WaterNetworkModel(inp_path)
|
||
_apply_hydraulic_options(wn, option_values)
|
||
# Pre-insert ALL mid-nodes so every simulation runs on the same topology,
|
||
# regardless of which worker handles which task.
|
||
if candidate_centers is not None:
|
||
for center in candidate_centers:
|
||
ensure_mid_node(wn, center)
|
||
_SIGNATURE_WORKER_DATA = {
|
||
"wn": wn,
|
||
"leak_mag": leak_mag,
|
||
"sensor_name": sensor_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(
|
||
data["wn"],
|
||
data["leak_mag"],
|
||
center_name,
|
||
data["sensor_name"],
|
||
file_prefix=temp_prefix,
|
||
)
|
||
return center_name, pressure_output.to_numpy()
|
||
|
||
|
||
def pick_pipe(all_pipes, pipe_diameter, limited_diameter):
|
||
candidate_pipe = []
|
||
for each_pipe in all_pipes:
|
||
if pipe_diameter[each_pipe] >= limited_diameter:
|
||
candidate_pipe.append(each_pipe)
|
||
return candidate_pipe
|
||
|
||
|
||
def cal_possible_pipe(leak_flow, all_pipe, pipe_diameter):
|
||
basic_pressure = 10 # 基础压力
|
||
discharge_coeff = 0.6 # 经验系数
|
||
break_area_ratio = 1 # 爆管面积比 0.5 1.25
|
||
break_area = leak_flow / (
|
||
discharge_coeff * math.sqrt(2 * basic_pressure * 9.81)
|
||
) # 爆管面积 m3/h
|
||
"""break_area_diameter = math.sqrt(4 * break_area / math.pi)
|
||
min_diameter = (math.ceil(1000 * break_area_diameter / break_area_ratio)) / 1000"""
|
||
break_area_diameter = math.sqrt(
|
||
4 * break_area / math.pi / break_area_ratio
|
||
) # 爆管直径
|
||
min_diameter = (math.ceil(1000 * break_area_diameter)) / 1000 # 向上取整
|
||
new_all_pipe = pick_pipe(all_pipe, pipe_diameter, min_diameter)
|
||
return new_all_pipe, min_diameter
|
||
|
||
|
||
def extract_links(data, link_types, direction):
|
||
return [
|
||
link
|
||
for res_data in data.values()
|
||
for link_type in link_types
|
||
for link in res_data[link_type][direction]
|
||
]
|
||
|
||
|