api_graph.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. from django.contrib import auth
  2. from rest_framework.views import APIView
  3. from rest_framework.response import Response
  4. from rest_framework import status
  5. from rest_framework.authtoken.models import Token
  6. from rest_framework.authentication import BasicAuthentication, TokenAuthentication
  7. from .serializers import UserRegisterSerializer
  8. from django.middleware.csrf import get_token
  9. from django.contrib.auth import login
  10. from api.utils import *
  11. from api.models import Result, Graph, GraphToken, Plan
  12. from random import randint
  13. import logging
  14. import json, csv
  15. logger = logging.getLogger("graph")
  16. def build_adjacency_lists(nodeJson, edgeJson):
  17. """ 构建类型化邻接表 """
  18. adj = {n['id']: [] for n in nodeJson}
  19. type_map = {n['id']: n['type'] for n in nodeJson}
  20. for edge in edgeJson:
  21. src, dst = edge['from'], edge['to']
  22. src_type = type_map[src]
  23. dst_type = type_map[dst]
  24. # 根据类型过滤连接
  25. if src_type == 'S' and dst_type == 'D':
  26. adj[src].append(dst)
  27. elif src_type == 'D' and dst_type in ('D', 'I'):
  28. adj[src].append(dst)
  29. # if edge['meta'][0].get('optimize') != 'new': # 双向处理
  30. # if dst_type == 'S' and src_type == 'D':
  31. # adj[dst].append(src)
  32. # elif dst_type == 'D' and src_type in ('D', 'I'):
  33. # adj[dst].append(src)
  34. return adj, type_map
  35. def parallel_chain_search(s_id, adj, type_map):
  36. """ 并行化路径搜索 """
  37. from collections import deque
  38. chains = []
  39. queue = deque([(s_id, 1)]) # (current_id, path_length)
  40. visited = {s_id: 1} # 节点: 到达时的路径长度
  41. while queue:
  42. nid, length = queue.popleft()
  43. if length > 4: # 限制最大深度为4层(总长度5)
  44. continue
  45. for neighbor in adj.get(nid, []):
  46. n_type = type_map[neighbor]
  47. # 剪枝规则
  48. if n_type == 'I' and length >= 2:
  49. chains.append(length + 1)
  50. continue
  51. if neighbor not in visited or visited[neighbor] > length + 1:
  52. visited[neighbor] = length + 1
  53. queue.append((neighbor, length + 1))
  54. return chains
  55. class ViewGraphByToken(APIView):
  56. # 通过验证码看图——供VR使用
  57. authentication_classes = []
  58. permission_classes = []
  59. def get(self, request):
  60. print(request.GET)
  61. requestToken = request.GET.get('token')
  62. print(requestToken)
  63. try:
  64. token = GraphToken.objects.get(token=str(requestToken))
  65. except GraphToken.DoesNotExist:
  66. return failed(message="Token不存在")
  67. if token.checkExpire():
  68. return failed(message="Token已过期")
  69. graph = token.graph
  70. return success(data={
  71. 'nodes': graph.nodes,
  72. 'edges': graph.edges,
  73. })
  74. class GenerateGraph(APIView):
  75. # 生成图数据
  76. def get(self, request):
  77. user = request.user
  78. method = request.GET.get('method') # 以何种视图查看
  79. planId = request.GET.get('plan')
  80. try:
  81. plan = Plan.objects.get(id=planId)
  82. except Plan.DoesNotExist:
  83. print("获取结果的Plan失败")
  84. return failed(message="无法找到该结果对应规划")
  85. try:
  86. result = plan.own_result
  87. except Result.DoesNotExist:
  88. logger.error(f"获取任务{plan.mission.id}规划{plan.id}的结果失败,结果不存在")
  89. return failed(message="结果不存在")
  90. # 图表显示不生成图,仅做统计计算
  91. if method == 'chart':
  92. nodeJson = result.nodeFile.toJson()
  93. edgeJson = result.edgeFile.toJson()
  94. # 1. 构建类型化邻接表(O(E)时间复杂度)
  95. adj, type_map = build_adjacency_lists(nodeJson, edgeJson)
  96. # 2. 并行计算度数分布(O(N)时间复杂度)
  97. from joblib import Parallel, delayed
  98. s_nodes = [n['id'] for n in nodeJson if n['type'] == 'S']
  99. d_nodes = [n['id'] for n in nodeJson if n['type'] == 'D']
  100. i_nodes = [n['id'] for n in nodeJson if n['type'] == 'I']
  101. # 度数统计
  102. degree_bins = lambda deg: min(deg, 10)
  103. s_degrees = [degree_bins(len(adj[nid])) for nid in s_nodes]
  104. d_degrees = [degree_bins(len(adj[nid])) for nid in d_nodes]
  105. i_degrees = [degree_bins(len(adj[nid])) for nid in i_nodes]
  106. # 3. 并行路径搜索(使用多线程加速)
  107. from concurrent.futures import ThreadPoolExecutor
  108. all_chains = []
  109. with ThreadPoolExecutor(max_workers=8) as executor:
  110. futures = [executor.submit(parallel_chain_search, s_id, adj, type_map)
  111. for s_id in s_nodes]
  112. for future in futures:
  113. all_chains.extend(future.result())
  114. # 统计结果(O(L)时间复杂度)
  115. chains_num = len(all_chains)
  116. len_dist = [0]*5
  117. for l in all_chains:
  118. len_dist[min(l-1, 4)] += 1 # 1-5层对应索引0-4
  119. avg_len = round(sum(all_chains)/chains_num, 2) if chains_num > 0 else 0.0
  120. data = {
  121. 'sNodesNum': len(s_nodes),
  122. 'dNodesNum': len(d_nodes),
  123. 'iNodesNum': len(i_nodes),
  124. 'sDegree': [s_degrees.count(i) for i in range(11)],
  125. 'dDegree': [d_degrees.count(i) for i in range(11)],
  126. 'iDegree': [i_degrees.count(i) for i in range(11)],
  127. 'chains': {
  128. 'num': chains_num,
  129. 'averageLength': avg_len,
  130. 'numByLength': len_dist
  131. }
  132. }
  133. # 根据计算类型的不同分别计算特殊数据
  134. if result.plan.algorithm.type in ['optimize', 'predict'] :
  135. # 拓扑优化算法应该统计原有图的数据与优化后图的数据,优化应该仅优化边
  136. from concurrent.futures import ThreadPoolExecutor
  137. def is_new_edge(edge):
  138. """ 判断是否为新增边 """
  139. return any(meta.get('optimize') == 'new' for meta in edge['meta'])
  140. def build_new_adjacency(nodeJson, edgeJson):
  141. """ 构建优化后的邻接表 """
  142. new_adj = {n['id']: [] for n in nodeJson}
  143. for edge in edgeJson:
  144. if is_new_edge(edge):
  145. src, dst = edge['from'], edge['to']
  146. new_adj[src].append(dst)
  147. new_adj[dst].append(src) # 无向图处理
  148. return new_adj
  149. # 在chart处理分支中添加
  150. if result.plan.algorithm.type in ['optimize', 'predict']:
  151. # 构建新邻接表
  152. new_adj = build_new_adjacency(nodeJson, edgeJson)
  153. # 并行计算新链路
  154. with ThreadPoolExecutor(max_workers=8) as executor:
  155. futures = [executor.submit(parallel_chain_search, s_id, new_adj, type_map)
  156. for s_id in s_nodes]
  157. new_chains = []
  158. for future in futures:
  159. new_chains.extend(future.result())
  160. # 统计新链路
  161. new_chains_num = len(new_chains)
  162. new_len_dist = [0]*5
  163. for l in new_chains:
  164. new_len_dist[min(l-1,4)] += 1
  165. new_avg_len = round(sum(new_chains)/new_chains_num,2) if new_chains_num else 0
  166. data['newChains'] = {
  167. 'num': new_chains_num,
  168. 'averageLength': new_avg_len,
  169. 'numByLength': new_len_dist
  170. }
  171. return success(data=data)
  172. # 如果已经生成过图数据,则直接生成新的验证码
  173. if result.own_graphs.exists():
  174. graph = result.own_graphs.first()
  175. if method == 'web' or method == '3D' or method == '3d':
  176. return success(data={
  177. 'nodes': graph.nodes,
  178. 'edges': graph.edges,
  179. })
  180. elif method == 'VR':
  181. token = graph.generateToken()
  182. return success(data={
  183. 'token': token,
  184. })
  185. # 不存在则需要重新生成图
  186. else:
  187. nodeJson = result.nodeFile.toJson()
  188. edgeJson = result.edgeFile.toJson()
  189. ######测试用,添加几个标签
  190. # for node in nodeJson:
  191. # node['meta'].append({'optimize': 'new'})
  192. # node['meta'].append({'group': randint(1,5)})
  193. # node['meta'].append({'predict': 'new'})
  194. # for edge in edgeJson:
  195. # edge['meta'].append({'optimize': 'new'})
  196. # edge['meta'].append({'predict': 'new'})
  197. ########################
  198. # 两种3D视图的数据结构应该是一样的
  199. if result.plan.algorithm.type == 'optimize':
  200. # 拓扑优化算法生成的结果,只有网络结构数据
  201. # 检查合法性
  202. for node in nodeJson:
  203. if not 'id' in node or not 'type' in node:
  204. return failed(message="拓扑优化结果的节点文件存在问题")
  205. for edge in edgeJson:
  206. if not 'from' in edge or not 'to' in edge:
  207. return failed(message="边文件存在问题")
  208. # 对于优化算法,边的属性中应该有新旧的区分
  209. missingLabel = True
  210. for meta in edge['meta']:
  211. if 'optimize' in meta and meta['optimize'] in ['new', 'old']:
  212. missingLabel = False
  213. if missingLabel:
  214. return failed(message="无拓扑优化标签")
  215. elif result.plan.algorithm.type == 'group':
  216. # 功能体探测算法生成的结果
  217. # 检查合法性
  218. for node in nodeJson:
  219. if not 'id' in node or not 'type' in node:
  220. return failed(message="节点文件存在问题")
  221. # 对于功能体探测算法,节点的属性中应有功能体的编号
  222. missingLabel = True
  223. for meta in node['meta']:
  224. if 'group' in meta and type(meta['group']) == int:
  225. missingLabel = False
  226. if missingLabel:
  227. # 存在节点没有功能体标签,则该节点应被视为孤立节点
  228. # 孤立节点添加一个-1的group表示孤立
  229. node['meta'].append({'group': -1})
  230. # return failed(message="无功能体标签")
  231. for edge in edgeJson:
  232. if not 'from' in edge or not 'to' in edge:
  233. return failed(message="边文件存在问题")
  234. elif result.plan.algorithm.type == 'predict':
  235. # 链路演化预测算法生成的结果
  236. # 检查合法性
  237. for node in nodeJson:
  238. if not 'id' in node or not 'type' in node:
  239. return failed(message="节点文件存在问题")
  240. # 对于演化预测算法,节点的属性中应有新旧区分
  241. # 演化预测和拓扑优化节点本身都没有meta属性,仅对边有区分
  242. # missingLabel = True
  243. # for meta in node['meta']:
  244. # if 'predict' in meta and meta['predict'] in ['new', 'old']:
  245. # missingLabel = False
  246. # if missingLabel:
  247. # return failed(message="无演化预测标签")
  248. for edge in edgeJson:
  249. if not 'from' in edge or not 'to' in edge:
  250. return failed(message="边文件存在问题")
  251. # 对于演化预测算法,边的属性中应有新旧区分
  252. for meta in edge['meta']:
  253. if 'predict' in meta and meta['predict'] in ['new', 'old']:
  254. missingLabel = False
  255. # 暂时取消标签检测,用于生成图
  256. missingLabel = False
  257. if missingLabel:
  258. return failed(message="无演化预测标签")
  259. graph = Graph.objects.createFromResult(result)
  260. graph.save()
  261. if method == 'web':
  262. return success(data={
  263. 'nodes': graph.nodes,
  264. 'edges': graph.edges,
  265. })
  266. if method == 'VR':
  267. token = graph.generateToken()
  268. return success(data={
  269. 'token': token,
  270. })