|
@@ -17,6 +17,54 @@ import json, csv
|
|
|
|
|
|
logger = logging.getLogger("graph")
|
|
|
|
|
|
+
|
|
|
+def build_adjacency_lists(nodeJson, edgeJson):
|
|
|
+ """ 构建类型化邻接表 """
|
|
|
+ adj = {n['id']: [] for n in nodeJson}
|
|
|
+ type_map = {n['id']: n['type'] for n in nodeJson}
|
|
|
+
|
|
|
+ for edge in edgeJson:
|
|
|
+ src, dst = edge['from'], edge['to']
|
|
|
+ src_type = type_map[src]
|
|
|
+ dst_type = type_map[dst]
|
|
|
+
|
|
|
+ # 根据类型过滤连接
|
|
|
+ if src_type == 'S' and dst_type == 'D':
|
|
|
+ adj[src].append(dst)
|
|
|
+ elif src_type == 'D' and dst_type in ('D', 'I'):
|
|
|
+ adj[src].append(dst)
|
|
|
+ # if edge['meta'][0].get('optimize') != 'new': # 双向处理
|
|
|
+ # if dst_type == 'S' and src_type == 'D':
|
|
|
+ # adj[dst].append(src)
|
|
|
+ # elif dst_type == 'D' and src_type in ('D', 'I'):
|
|
|
+ # adj[dst].append(src)
|
|
|
+ return adj, type_map
|
|
|
+
|
|
|
+def parallel_chain_search(s_id, adj, type_map):
|
|
|
+ """ 并行化路径搜索 """
|
|
|
+ from collections import deque
|
|
|
+ chains = []
|
|
|
+ queue = deque([(s_id, 1)]) # (current_id, path_length)
|
|
|
+ visited = {s_id: 1} # 节点: 到达时的路径长度
|
|
|
+
|
|
|
+ while queue:
|
|
|
+ nid, length = queue.popleft()
|
|
|
+ if length > 4: # 限制最大深度为4层(总长度5)
|
|
|
+ continue
|
|
|
+
|
|
|
+ for neighbor in adj.get(nid, []):
|
|
|
+ n_type = type_map[neighbor]
|
|
|
+ # 剪枝规则
|
|
|
+ if n_type == 'I' and length >= 2:
|
|
|
+ chains.append(length + 1)
|
|
|
+ continue
|
|
|
+ if neighbor not in visited or visited[neighbor] > length + 1:
|
|
|
+ visited[neighbor] = length + 1
|
|
|
+ queue.append((neighbor, length + 1))
|
|
|
+
|
|
|
+ return chains
|
|
|
+
|
|
|
+
|
|
|
class ViewGraphByToken(APIView):
|
|
|
# 通过验证码看图——供VR使用
|
|
|
authentication_classes = []
|
|
@@ -59,122 +107,96 @@ class GenerateGraph(APIView):
|
|
|
if method == 'chart':
|
|
|
nodeJson = result.nodeFile.toJson()
|
|
|
edgeJson = result.edgeFile.toJson()
|
|
|
- def isOriginEdge(edge):
|
|
|
- for meta in edge['meta']:
|
|
|
- if meta.get('optimize') == 'new' or meta.get('predict') == 'new':
|
|
|
- return False
|
|
|
- return True
|
|
|
- # 需要完善图表视图统计数据,根据具体图表类型决定
|
|
|
- # 通用图数据统计,包括SDI节点数量、边数量,各类型节点度数分布
|
|
|
- # 平均链长度,聚类系数即D节点间连接紧密程度
|
|
|
- # 分别统计SDI节点的数量和度数:sNodes,dNodes,iNodes
|
|
|
- sDegree = dDegree = iDegree = [0 for i in range(11)] # 度的取值范围0~10,共11个元素
|
|
|
- sNodes = [node for node in nodeJson if node['type'] == 'S']
|
|
|
- for s in sNodes:
|
|
|
- degree = len([edge for edge in edgeJson if edge['from'] == s['id'] or edge['to'] == s['id']])
|
|
|
- if degree <= 10:
|
|
|
- sDegree[degree] += 1
|
|
|
- else:
|
|
|
- sDegree[10] += 1
|
|
|
- dNodes = [node for node in nodeJson if node['type'] == 'D']
|
|
|
- for d in dNodes:
|
|
|
- degree = len([edge for edge in edgeJson if edge['from'] == d['id'] or edge['to'] == d['id']])
|
|
|
- if degree <= 10:
|
|
|
- dDegree[degree] += 1
|
|
|
- else:
|
|
|
- dDegree[10] += 1
|
|
|
- iNodes = [node for node in nodeJson if node['type'] == 'I']
|
|
|
- for i in iNodes:
|
|
|
- degree = len([edge for edge in edgeJson if edge['from'] == i['id'] or edge['to'] == i['id']])
|
|
|
- if degree <= 10:
|
|
|
- iDegree[degree] += 1
|
|
|
- else:
|
|
|
- iDegree[10] += 1
|
|
|
- # 查找所有OODA链路:chains
|
|
|
- chains = []
|
|
|
- for s in sNodes:
|
|
|
- stack = [(s, [s])] # (当前节点, 路径, 已访问节点)
|
|
|
- while stack:
|
|
|
- current, path = stack.pop()
|
|
|
- # 必须限制链路长度,否则无法结束,除限制长度外还可尝试将不同顺序节点的链路视为同一条,从而减少链路数量
|
|
|
- if len(path) > 5:
|
|
|
- continue
|
|
|
- # 终止条件:到达I节点
|
|
|
- if current['type'] == 'I':
|
|
|
- chains.append(path)
|
|
|
- continue
|
|
|
-
|
|
|
- # 遍历邻居,注意通用计算只统计原始图
|
|
|
- neighbors = [edge['to'] for edge in edgeJson if (isOriginEdge(edge) and edge['from'] == current['id'])] + [edge['from'] for edge in edgeJson if (isOriginEdge(edge) and edge['to'] == current['id'])]
|
|
|
- for neighborId in neighbors:
|
|
|
- neighbor = [node for node in nodeJson if node.get('id') == neighborId]
|
|
|
- if len(neighbor) != 1:
|
|
|
- return failed(message="查找OODA链路时出错:不存在的邻居节点")
|
|
|
- neighbor = neighbor[0]
|
|
|
- if neighbor in path: continue # 避免环路
|
|
|
- # 不能连线的几种情况
|
|
|
- if current['type'] == 'S' and neighbor['type'] == 'I':
|
|
|
- continue
|
|
|
- if current['type'] == 'D' and neighbor['type'] == 'S':
|
|
|
- continue
|
|
|
- stack.append((neighbor, path + [neighbor]))
|
|
|
- chainsNum = len(chains)
|
|
|
- chainsNumByLength = [0 for i in range(5)]
|
|
|
- for chain in chains:
|
|
|
- length = len(chain)
|
|
|
- if length >= 5:
|
|
|
- length = 5
|
|
|
- chainsNumByLength[length-1] += 1
|
|
|
- averageChainsLength = round(sum([len(chain) for chain in chains]) / chainsNum, 2)
|
|
|
+
|
|
|
+ # 1. 构建类型化邻接表(O(E)时间复杂度)
|
|
|
+ adj, type_map = build_adjacency_lists(nodeJson, edgeJson)
|
|
|
+
|
|
|
+ # 2. 并行计算度数分布(O(N)时间复杂度)
|
|
|
+ from joblib import Parallel, delayed
|
|
|
+ s_nodes = [n['id'] for n in nodeJson if n['type'] == 'S']
|
|
|
+ d_nodes = [n['id'] for n in nodeJson if n['type'] == 'D']
|
|
|
+ i_nodes = [n['id'] for n in nodeJson if n['type'] == 'I']
|
|
|
+
|
|
|
+ # 度数统计
|
|
|
+ degree_bins = lambda deg: min(deg, 10)
|
|
|
+ s_degrees = [degree_bins(len(adj[nid])) for nid in s_nodes]
|
|
|
+ d_degrees = [degree_bins(len(adj[nid])) for nid in d_nodes]
|
|
|
+ i_degrees = [degree_bins(len(adj[nid])) for nid in i_nodes]
|
|
|
+
|
|
|
+ # 3. 并行路径搜索(使用多线程加速)
|
|
|
+ from concurrent.futures import ThreadPoolExecutor
|
|
|
+ all_chains = []
|
|
|
+ with ThreadPoolExecutor(max_workers=8) as executor:
|
|
|
+ futures = [executor.submit(parallel_chain_search, s_id, adj, type_map)
|
|
|
+ for s_id in s_nodes]
|
|
|
+ for future in futures:
|
|
|
+ all_chains.extend(future.result())
|
|
|
+
|
|
|
+ # 统计结果(O(L)时间复杂度)
|
|
|
+ chains_num = len(all_chains)
|
|
|
+ len_dist = [0]*5
|
|
|
+ for l in all_chains:
|
|
|
+ len_dist[min(l-1, 4)] += 1 # 1-5层对应索引0-4
|
|
|
+
|
|
|
+ avg_len = round(sum(all_chains)/chains_num, 2) if chains_num > 0 else 0.0
|
|
|
+
|
|
|
data = {
|
|
|
- 'sNodesNum': len(sNodes),
|
|
|
- 'dNodesNum': len(dNodes),
|
|
|
- 'iNodesNum': len(iNodes),
|
|
|
- 'sDegree': sDegree,
|
|
|
- 'dDegree': dDegree,
|
|
|
- 'iDegree': iDegree,
|
|
|
+ 'sNodesNum': len(s_nodes),
|
|
|
+ 'dNodesNum': len(d_nodes),
|
|
|
+ 'iNodesNum': len(i_nodes),
|
|
|
+ 'sDegree': [s_degrees.count(i) for i in range(11)],
|
|
|
+ 'dDegree': [d_degrees.count(i) for i in range(11)],
|
|
|
+ 'iDegree': [i_degrees.count(i) for i in range(11)],
|
|
|
'chains': {
|
|
|
- 'num': chainsNum,
|
|
|
- 'averageLength': averageChainsLength,
|
|
|
- 'numByLength': chainsNumByLength,
|
|
|
- },
|
|
|
+ 'num': chains_num,
|
|
|
+ 'averageLength': avg_len,
|
|
|
+ 'numByLength': len_dist
|
|
|
+ }
|
|
|
}
|
|
|
# 根据计算类型的不同分别计算特殊数据
|
|
|
if result.plan.algorithm.type in ['optimize', 'predict'] :
|
|
|
# 拓扑优化算法应该统计原有图的数据与优化后图的数据,优化应该仅优化边
|
|
|
- newChains = []
|
|
|
- for s in sNodes:
|
|
|
- stack = [(s, [s])] # (当前节点, 路径, 已访问节点)
|
|
|
- while stack:
|
|
|
- current, path = stack.pop()
|
|
|
-
|
|
|
- # 终止条件:到达I节点
|
|
|
- if current['type'] == 'I':
|
|
|
- newChains.append(path)
|
|
|
- continue
|
|
|
-
|
|
|
- # 遍历邻居,注意优化计算只统计新图
|
|
|
- neighbors = [edge['to'] for edge in edgeJson if (not isOriginEdge(edge) and edge['from'] == current['id'])] + [edge['from'] for edge in edgeJson if (not isOriginEdge(edge) and edge['to'] == current['id'])]
|
|
|
- for neighborId in neighbors:
|
|
|
- neighbor = [node for node in nodeJson if node.get('id') == neighborId]
|
|
|
- if len(neighbor) != 1:
|
|
|
- return failed(message="查找OODA链路时出错:不存在的邻居节点")
|
|
|
- neighbor = neighbor[0]
|
|
|
- if neighbor in path: continue # 避免环路
|
|
|
- # 不能连线的几种情况
|
|
|
- if current['type'] == 'S' and neighbor['type'] == 'I':
|
|
|
- continue
|
|
|
- if current['type'] == 'D' and neighbor['type'] == 'S':
|
|
|
- continue
|
|
|
- stack.append((neighbor, path + [neighbor]))
|
|
|
- newChainsNum = len(newChains)
|
|
|
- newAverageChainsLength = 0
|
|
|
- if newChainsNum != 0:
|
|
|
- newAverageChainsLength = round(sum([len(chain) for chain in newChains]) / newChainsNum, 2)
|
|
|
- data['newChains'] = {
|
|
|
- 'num': newChainsNum,
|
|
|
- 'averageLength': newAverageChainsLength,
|
|
|
- }
|
|
|
+ from concurrent.futures import ThreadPoolExecutor
|
|
|
+
|
|
|
+ def is_new_edge(edge):
|
|
|
+ """ 判断是否为新增边 """
|
|
|
+ return any(meta.get('optimize') == 'new' for meta in edge['meta'])
|
|
|
+
|
|
|
+ def build_new_adjacency(nodeJson, edgeJson):
|
|
|
+ """ 构建优化后的邻接表 """
|
|
|
+ new_adj = {n['id']: [] for n in nodeJson}
|
|
|
+ for edge in edgeJson:
|
|
|
+ if is_new_edge(edge):
|
|
|
+ src, dst = edge['from'], edge['to']
|
|
|
+ new_adj[src].append(dst)
|
|
|
+ new_adj[dst].append(src) # 无向图处理
|
|
|
+ return new_adj
|
|
|
+
|
|
|
+ # 在chart处理分支中添加
|
|
|
+ if result.plan.algorithm.type in ['optimize', 'predict']:
|
|
|
+ # 构建新邻接表
|
|
|
+ new_adj = build_new_adjacency(nodeJson, edgeJson)
|
|
|
+
|
|
|
+ # 并行计算新链路
|
|
|
+ with ThreadPoolExecutor(max_workers=8) as executor:
|
|
|
+ futures = [executor.submit(parallel_chain_search, s_id, new_adj, type_map)
|
|
|
+ for s_id in s_nodes]
|
|
|
+ new_chains = []
|
|
|
+ for future in futures:
|
|
|
+ new_chains.extend(future.result())
|
|
|
+
|
|
|
+ # 统计新链路
|
|
|
+ new_chains_num = len(new_chains)
|
|
|
+ new_len_dist = [0]*5
|
|
|
+ for l in new_chains:
|
|
|
+ new_len_dist[min(l-1,4)] += 1
|
|
|
+ new_avg_len = round(sum(new_chains)/new_chains_num,2) if new_chains_num else 0
|
|
|
+
|
|
|
+ data['newChains'] = {
|
|
|
+ 'num': new_chains_num,
|
|
|
+ 'averageLength': new_avg_len,
|
|
|
+ 'numByLength': new_len_dist
|
|
|
+ }
|
|
|
return success(data=data)
|
|
|
|
|
|
# 如果已经生成过图数据,则直接生成新的验证码
|
|
@@ -260,10 +282,13 @@ class GenerateGraph(APIView):
|
|
|
if not 'from' in edge or not 'to' in edge:
|
|
|
return failed(message="边文件存在问题")
|
|
|
# 对于演化预测算法,边的属性中应有新旧区分
|
|
|
- missingLabel = True
|
|
|
+
|
|
|
for meta in edge['meta']:
|
|
|
if 'predict' in meta and meta['predict'] in ['new', 'old']:
|
|
|
missingLabel = False
|
|
|
+
|
|
|
+ # 暂时取消标签检测,用于生成图
|
|
|
+ missingLabel = False
|
|
|
if missingLabel:
|
|
|
return failed(message="无演化预测标签")
|
|
|
graph = Graph.objects.createFromResult(result)
|