CommunicationProcessor.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. from lea import Lea
  2. from Attack.MembersMgmtCommAttack import MessageType
  3. from Attack.MembersMgmtCommAttack import Message
  4. # needed because of machine inprecision. E.g A time difference of 0.1s is stored as >0.1s
  5. EPS_TOLERANCE = 1e-13 # works for a difference of 0.1, no less
  6. class CommunicationProcessor():
  7. """
  8. Class to process parsed input CSV/XML data and retrieve a mapping or other information.
  9. """
  10. def __init__(self, packets:list, mtypes:dict):
  11. self.packets = packets
  12. self.mtypes = mtypes
  13. def set_mapping(self, packets: list, mapped_ids: dict):
  14. """
  15. Set the selected mapping for this communication processor.
  16. :param packets: all packets contained in the mapped time frame
  17. :param mapped_ids: the chosen IDs
  18. """
  19. self.packets = packets
  20. self.init_ids = set(mapped_ids.keys())
  21. def find_interval_with_most_comm(self, number_ids: int, max_int_time: float):
  22. """
  23. Finds a time interval of the given seconds where the given number of IDs communicate among themselves the most.
  24. :param packets: The packets containing the communication
  25. :param number_ids: The number of IDs that are to be considered
  26. :param max_int_time: A short description of the attack.
  27. :return: A triple consisting of the IDs, as well as start and end idx with respect to the given packets.
  28. """
  29. packets = self.packets
  30. mtypes = self.mtypes
  31. def get_nez_comm_counts(comm_counts: dict):
  32. """
  33. Filters out all msg_counts that have 0 as value
  34. """
  35. nez_comm_counts = dict()
  36. for id_ in comm_counts.keys():
  37. count = comm_counts[id_]
  38. if count > 0:
  39. nez_comm_counts[id_] = count
  40. return nez_comm_counts
  41. def greater_than(a: float, b: float):
  42. """
  43. A greater than operator desgined to handle slight machine inprecision up to EPS_TOLERANCE.
  44. :return: True if a > b, otherwise False
  45. """
  46. return b - a < -EPS_TOLERANCE
  47. def change_comm_counts(comm_counts: dict, idx: int, add=True):
  48. """
  49. Changes the communication count, stored in comm_counts, of the initiating ID with respect to the
  50. packet specified by the given index. If add is True, 1 is added to the value, otherwise 1 is subtracted.
  51. """
  52. change = 1 if add else -1
  53. mtype = mtypes[int(packets[idx]["Type"])]
  54. id_src, id_dst = packets[idx]["Src"], packets[idx]["Dst"]
  55. if mtype in {MessageType.SALITY_HELLO, MessageType.SALITY_NL_REQUEST}:
  56. if id_src in comm_counts:
  57. comm_counts[id_src] += change
  58. elif change > 0:
  59. comm_counts[id_src] = 1
  60. elif mtype in {MessageType.SALITY_HELLO_REPLY, MessageType.SALITY_NL_REPLY}:
  61. if id_dst in comm_counts:
  62. comm_counts[id_dst] += change
  63. elif change > 0:
  64. comm_counts[id_dst] = 1
  65. def get_comm_count_first_ids(comm_counts: list):
  66. """
  67. Finds the IDs that communicate among themselves the most with respect to the given message counts.
  68. :param msg_counts: a sorted list of message counts where each entry is a tuple of key and value
  69. :return: The picked IDs and their total message count as a tuple
  70. """
  71. # if order of most messages is important, use an additional list
  72. picked_ids = {}
  73. total_comm_count = 0
  74. # iterate over every message count
  75. for i, comm in enumerate(comm_counts):
  76. count_picked_ids = len(picked_ids)
  77. # if enough IDs have been found, stop
  78. if count_picked_ids >= number_ids:
  79. break
  80. picked_ids[comm[0]] = comm[1]
  81. total_comm_count += comm[1]
  82. return picked_ids, total_comm_count
  83. # first find all possible intervals that contain enough IDs that initiate communication
  84. idx_low, idx_high = 0, 0
  85. comm_counts = dict()
  86. possible_intervals = []
  87. # Iterate over all packets from start to finish and process the info of each packet
  88. # If time of packet within time interval, update the message count for this communication
  89. # If time of packet exceeds time interval, substract from the message count for this communication
  90. while True:
  91. if idx_high < len(packets):
  92. cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
  93. # if current interval time exceeds time interval, save the message counts if appropriate, or stop if no more packets
  94. if greater_than(cur_int_time, max_int_time) or idx_high >= len(packets):
  95. # get all message counts for communications that took place in the current intervall
  96. nez_comm_counts = get_nez_comm_counts(comm_counts)
  97. # if we have enough IDs as specified by the caller, mark as possible interval
  98. if len(nez_comm_counts) >= number_ids:
  99. # possible_intervals.append((nez_msg_counts, packets[idx_low]["Time"], packets[idx_high-1]["Time"]))
  100. possible_intervals.append((nez_comm_counts, idx_low, idx_high - 1))
  101. if idx_high >= len(packets):
  102. break
  103. # let idx_low "catch up" so that the current interval time fits into the interval time specified by the caller
  104. while greater_than(cur_int_time, max_int_time):
  105. change_comm_counts(comm_counts, idx_low, add=False)
  106. idx_low += 1
  107. cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
  108. # consume the new packet at idx_high and process its information
  109. change_comm_counts(comm_counts, idx_high)
  110. idx_high += 1
  111. # now find the interval in which as many IDs as specified communicate the most in the given time interval
  112. summed_intervals = []
  113. sum_intervals_idxs = []
  114. cur_highest_sum = 0
  115. # for every interval compute the sum of id_counts of the first most communicative IDs and eventually find
  116. # the interval(s) with most communication and its IDs
  117. # on the side also store the communication count of the individual IDs
  118. for j, interval in enumerate(possible_intervals):
  119. comm_counts = interval[0].items()
  120. sorted_comm_counts = sorted(comm_counts, key=lambda x: x[1], reverse=True)
  121. picked_ids, comm_sum = get_comm_count_first_ids(sorted_comm_counts)
  122. if comm_sum == cur_highest_sum:
  123. summed_intervals.append({"IDs": picked_ids, "CommSum": comm_sum, "Start": interval[1], "End": interval[2]})
  124. elif comm_sum > cur_highest_sum:
  125. summed_intervals = []
  126. summed_intervals.append({"IDs": picked_ids, "CommSum": comm_sum, "Start": interval[1], "End": interval[2]})
  127. cur_highest_sum = comm_sum
  128. return summed_intervals
  129. def det_id_roles_and_msgs(self):
  130. """
  131. Determine the role of every mapped ID. The role can be initiator, responder or both.
  132. On the side also connect corresponding messages together to quickly find out
  133. which reply belongs to which request and vice versa.
  134. :return: a triple as (initiator IDs, responder IDs, messages)
  135. """
  136. mtypes = self.mtypes
  137. # setup initial variables and their values
  138. respnd_ids = set()
  139. # msgs --> the filtered messages, msg_id --> an increasing ID to give every message an artificial primary key
  140. msgs, msg_id = {}, 0
  141. # keep track of previous request to find connections
  142. prev_reqs = {}
  143. init_ids = self.init_ids
  144. # process every packet individually
  145. for packet in self.packets:
  146. id_src, id_dst, msg_type, time = packet["Src"], packet["Dst"], int(packet["Type"]), float(packet["Time"])
  147. # if if either one of the IDs is not mapped, continue
  148. if (id_src not in init_ids) and (id_dst not in init_ids):
  149. continue
  150. # convert message type number to enum type
  151. msg_type = mtypes[msg_type]
  152. # process a request
  153. if msg_type in {MessageType.SALITY_HELLO, MessageType.SALITY_NL_REQUEST}:
  154. if id_src not in init_ids:
  155. continue
  156. # process ID's role
  157. respnd_ids.add(id_dst)
  158. # convert the abstract message into a message object to handle it better
  159. msg_str = "{0}-{1}".format(id_src, id_dst)
  160. msg = Message(msg_id, id_src, id_dst, msg_type, time)
  161. msgs[msg_id] = msg
  162. prev_reqs[msg_str] = msg_id
  163. # process a reply
  164. elif msg_type in {MessageType.SALITY_HELLO_REPLY, MessageType.SALITY_NL_REPLY}:
  165. if id_dst not in init_ids:
  166. continue
  167. # process ID's role
  168. respnd_ids.add(id_src)
  169. # convert the abstract message into a message object to handle it better
  170. msg_str = "{0}-{1}".format(id_dst, id_src)
  171. # find the request message ID for this response and set its reference index
  172. refer_idx = prev_reqs[msg_str]
  173. msgs[refer_idx].refer_msg_id = msg_id
  174. # print(msgs[refer_idx])
  175. msg = Message(msg_id, id_src, id_dst, msg_type, time, refer_idx)
  176. msgs[msg_id] = msg
  177. # remove the request to this response from storage
  178. del(prev_reqs[msg_str])
  179. # for message ID only count actual messages
  180. if not msg_type == MessageType.TIMEOUT:
  181. msg_id += 1
  182. # store the retrieved information in this object for later use
  183. self.respnd_ids = sorted(respnd_ids)
  184. self.messages = msgs
  185. # return the retrieved information
  186. return self.init_ids, self.respnd_ids, msgs
  187. def det_ext_and_local_ids(self, prob_rspnd_local: int):
  188. """
  189. Map the given IDs to a locality (i.e. local or external} considering the given probabilities.
  190. :param comm_type: the type of communication (i.e. local, external or mixed)
  191. :param prob_rspnd_local: the probabilty that a responder is local
  192. """
  193. external_ids = set()
  194. local_ids = self.init_ids.copy()
  195. # set up probabilistic chooser
  196. rspnd_locality = Lea.fromValFreqsDict({"local": prob_rspnd_local*100, "external": (1-prob_rspnd_local)*100})
  197. # determine responder localities
  198. for id_ in self.respnd_ids:
  199. if id_ in local_ids or id_ in external_ids:
  200. continue
  201. pos = rspnd_locality.random()
  202. if pos == "local":
  203. local_ids.add(id_)
  204. elif pos == "external":
  205. external_ids.add(id_)
  206. self.local_ids, self.external_ids = local_ids, external_ids
  207. return self.local_ids, self.external_ids
  208. # def find_interval_with_most_comm(self, number_ids: int, max_int_time: float):
  209. # """
  210. # Finds a time interval of the given seconds where the given number of IDs communicate among themselves the most.
  211. # :param packets: The packets containing the communication
  212. # :param number_ids: The number of IDs that are to be considered
  213. # :param max_int_time: A short description of the attack.
  214. # :return: A triple consisting of the IDs, as well as start and end idx with respect to the given packets.
  215. # """
  216. # packets = self.packets
  217. # def get_nez_msg_counts(msg_counts: dict):
  218. # """
  219. # Filters out all msg_counts that have 0 as value
  220. # """
  221. # nez_msg_counts = dict()
  222. # for msg in msg_counts.keys():
  223. # count = msg_counts[msg]
  224. # if count > 0:
  225. # nez_msg_counts[msg] = count
  226. # return nez_msg_counts
  227. # def greater_than(a: float, b: float):
  228. # """
  229. # A greater than operator desgined to handle slight machine inprecision up to EPS_TOLERANCE.
  230. # :return: True if a > b, otherwise False
  231. # """
  232. # return b - a < -EPS_TOLERANCE
  233. # def change_msg_counts(msg_counts: dict, idx: int, add=True):
  234. # """
  235. # Changes the value of the message count of the message occuring in the packet specified by the given index.
  236. # Adds 1 if add is True and subtracts 1 otherwise.
  237. # """
  238. # change = 1 if add else -1
  239. # id_src, id_dst = packets[idx]["Src"], packets[idx]["Dst"]
  240. # src_to_dst = "{0}-{1}".format(id_src, id_dst)
  241. # dst_to_src = "{0}-{1}".format(id_dst, id_src)
  242. # if src_to_dst in msg_counts.keys():
  243. # msg_counts[src_to_dst] += change
  244. # elif dst_to_src in msg_counts.keys():
  245. # msg_counts[dst_to_src] += change
  246. # elif add:
  247. # msg_counts[src_to_dst] = 1
  248. # def count_ids_in_msg_counts(msg_counts: dict):
  249. # """
  250. # Counts all ids that are involved in messages with a non zero message count
  251. # """
  252. # ids = set()
  253. # for msg in msg_counts.keys():
  254. # src, dst = msg.split("-")
  255. # ids.add(dst)
  256. # ids.add(src)
  257. # return len(ids)
  258. # def get_msg_count_first_ids(msg_counts: list):
  259. # """
  260. # Finds the IDs that communicate among themselves the most with respect to the given message counts.
  261. # :param msg_counts: a sorted list of message counts where each entry is a tuple of key and value
  262. # :return: The picked IDs and their total message count as a tuple
  263. # """
  264. # # if order of most messages is important, use an additional list
  265. # picked_ids = set()
  266. # total_msg_count = 0
  267. # # iterate over every message count
  268. # for i, msg in enumerate(msg_counts):
  269. # count_picked_ids = len(picked_ids)
  270. # id_one, id_two = msg[0].split("-")
  271. # # if enough IDs have been found, stop
  272. # if count_picked_ids >= number_ids:
  273. # break
  274. # # if two IDs can be added without exceeding the desired number of IDs, add them
  275. # if count_picked_ids - 2 <= number_ids:
  276. # picked_ids.add(id_one)
  277. # picked_ids.add(id_two)
  278. # total_msg_count += msg[1]
  279. # # if there is only room for one more id to be added,
  280. # # find one that is already contained in the picked IDs
  281. # else:
  282. # for j, msg in enumerate(msg_counts[i:]):
  283. # id_one, id_two = msg[0].split("-")
  284. # if id_one in picked_ids:
  285. # picked_ids.add(id_two)
  286. # total_msg_count += msg[1]
  287. # break
  288. # elif id_two in picked_ids:
  289. # picked_ids.add(id_one)
  290. # total_msg_count += msg[1]
  291. # break
  292. # break
  293. # return picked_ids, total_msg_count
  294. # def get_indv_id_counts_and_comms(picked_ids: dict, msg_counts: dict):
  295. # """
  296. # Retrieves the total mentions of one ID in the communication pattern
  297. # and all communication entries that include only picked IDs.
  298. # """
  299. # indv_id_counts = {}
  300. # id_comms = set()
  301. # for msg in msg_counts:
  302. # ids = msg.split("-")
  303. # if ids[0] in picked_ids and ids[1] in picked_ids:
  304. # msg_other_dir = "{}-{}".format(ids[1], ids[0])
  305. # if (not msg in id_comms) and (not msg_other_dir in id_comms):
  306. # id_comms.add(msg)
  307. # for id_ in ids:
  308. # if id_ in indv_id_counts:
  309. # indv_id_counts[id_] += msg_counts[msg]
  310. # else:
  311. # indv_id_counts[id_] = msg_counts[msg]
  312. # return indv_id_counts, id_comms
  313. # # first find all possible intervals that contain enough IDs that communicate among themselves
  314. # idx_low, idx_high = 0, 0
  315. # msg_counts = dict()
  316. # possible_intervals = []
  317. # # Iterate over all packets from start to finish and process the info of each packet
  318. # # If time of packet within time interval, update the message count for this communication
  319. # # If time of packet exceeds time interval, substract from the message count for this communication
  320. # while True:
  321. # if idx_high < len(packets):
  322. # cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
  323. # # if current interval time exceeds time interval, save the message counts if appropriate, or stop if no more packets
  324. # if greater_than(cur_int_time, max_int_time) or idx_high >= len(packets):
  325. # # get all message counts for communications that took place in the current intervall
  326. # nez_msg_counts = get_nez_msg_counts(msg_counts)
  327. # # if we have enough IDs as specified by the caller, mark as possible interval
  328. # if count_ids_in_msg_counts(nez_msg_counts) >= number_ids:
  329. # # possible_intervals.append((nez_msg_counts, packets[idx_low]["Time"], packets[idx_high-1]["Time"]))
  330. # possible_intervals.append((nez_msg_counts, idx_low, idx_high - 1))
  331. # if idx_high >= len(packets):
  332. # break
  333. # # let idx_low "catch up" so that the current interval time fits into the interval time specified by the caller
  334. # while greater_than(cur_int_time, max_int_time):
  335. # change_msg_counts(msg_counts, idx_low, add=False)
  336. # idx_low += 1
  337. # cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
  338. # # consume the new packet at idx_high and process its information
  339. # change_msg_counts(msg_counts, idx_high)
  340. # idx_high += 1
  341. # # now find the interval in which as many IDs as specified communicate the most in the given time interval
  342. # summed_intervals = []
  343. # sum_intervals_idxs = []
  344. # cur_highest_sum = 0
  345. # # for every interval compute the sum of msg_counts of the first most communicative IDs and eventually find
  346. # # the interval(s) with most communication and its IDs
  347. # # on the side also store the communication count of the individual IDs
  348. # for j, interval in enumerate(possible_intervals):
  349. # msg_counts = interval[0].items()
  350. # sorted_msg_counts = sorted(msg_counts, key=lambda x: x[1], reverse=True)
  351. # picked_ids, msg_sum = get_msg_count_first_ids(sorted_msg_counts)
  352. # if msg_sum == cur_highest_sum:
  353. # summed_intervals.append({"IDs": picked_ids, "MsgSum": msg_sum, "Start": interval[1], "End": interval[2]})
  354. # sum_intervals_idxs.append(j)
  355. # elif msg_sum > cur_highest_sum:
  356. # summed_intervals = []
  357. # sum_intervals_idxs = [j]
  358. # summed_intervals.append({"IDs": picked_ids, "MsgSum": msg_sum, "Start": interval[1], "End": interval[2]})
  359. # cur_highest_sum = msg_sum
  360. # for j, interval in enumerate(summed_intervals):
  361. # idx = sum_intervals_idxs[j]
  362. # msg_counts_picked = possible_intervals[idx][0]
  363. # indv_id_counts, id_comms = get_indv_id_counts_and_comms(interval["IDs"], msg_counts_picked)
  364. # interval["IDs"] = indv_id_counts
  365. # interval["Comms"] = id_comms
  366. # return summed_intervals
  367. # def det_id_roles_and_msgs(self):
  368. # """
  369. # Determine the role of every mapped ID. The role can be initiator, responder or both.
  370. # On the side also connect corresponding messages together to quickly find out
  371. # which reply belongs to which request and vice versa.
  372. # :return: a 4-tuple as (initiator IDs, responder IDs, both IDs, messages)
  373. # """
  374. # mtypes = self.mtypes
  375. # # setup initial variables and their values
  376. # init_ids, respnd_ids, both_ids = set(), set(), set()
  377. # # msgs --> the filtered messages, msg_id --> an increasing ID to give every message an artificial primary key
  378. # msgs, msg_id = [], 0
  379. # # kepp track of previous request to find connections
  380. # prev_reqs = {}
  381. # all_init_ids = self.init_ids
  382. # packets = self.packets
  383. # def process_initiator(id_: str):
  384. # """
  385. # Process the given ID as initiator and update the above sets accordingly.
  386. # """
  387. # if id_ in both_ids:
  388. # pass
  389. # elif not id_ in respnd_ids:
  390. # init_ids.add(id_)
  391. # elif id_ in respnd_ids:
  392. # respnd_ids.remove(id_)
  393. # both_ids.add(id_)
  394. # def process_responder(id_: str):
  395. # """
  396. # Process the given ID as responder and update the above sets accordingly.
  397. # """
  398. # if id_ in both_ids:
  399. # pass
  400. # elif not id_ in init_ids:
  401. # respnd_ids.add(id_)
  402. # elif id_ in init_ids:
  403. # init_ids.remove(id_)
  404. # both_ids.add(id_)
  405. # # process every packet individually
  406. # for packet in packets:
  407. # id_src, id_dst, msg_type, time = packet["Src"], packet["Dst"], int(packet["Type"]), float(packet["Time"])
  408. # # if if either one of the IDs is not mapped, continue
  409. # if (not id_src in all_ids) or (not id_dst in all_ids):
  410. # continue
  411. # # convert message type number to enum type
  412. # msg_type = mtypes[msg_type]
  413. # # process a request
  414. # if msg_type in {MessageType.SALITY_HELLO, MessageType.SALITY_NL_REQUEST}:
  415. # # process each ID's role
  416. # process_initiator(id_src)
  417. # process_responder(id_dst)
  418. # # convert the abstract message into a message object to handle it better
  419. # msg_str = "{0}-{1}".format(id_src, id_dst)
  420. # msg = Message(msg_id, id_src, id_dst, msg_type, time)
  421. # msgs.append(msg)
  422. # prev_reqs[msg_str] = msg_id
  423. # # process a reply
  424. # elif msg_type in {MessageType.SALITY_HELLO_REPLY, MessageType.SALITY_NL_REPLY}:
  425. # # process each ID's role
  426. # process_initiator(id_dst)
  427. # process_responder(id_src)
  428. # # convert the abstract message into a message object to handle it better
  429. # msg_str = "{0}-{1}".format(id_dst, id_src)
  430. # # find the request message ID for this response and set its reference index
  431. # refer_idx = prev_reqs[msg_str]
  432. # msgs[refer_idx].refer_msg_id = msg_id
  433. # # print(msgs[refer_idx])
  434. # msg = Message(msg_id, id_src, id_dst, msg_type, time, refer_idx)
  435. # msgs.append(msg)
  436. # # remove the request to this response from storage
  437. # del(prev_reqs[msg_str])
  438. # # for message ID only count actual messages
  439. # if not msg_type == MessageType.TIMEOUT:
  440. # msg_id += 1
  441. # # store the retrieved information in this object for later use
  442. # self.init_ids, self.respnd_ids, self.both_ids = sorted(init_ids), sorted(respnd_ids), sorted(both_ids)
  443. # self.messages = msgs
  444. # # return the retrieved information
  445. # return init_ids, respnd_ids, both_ids, msgs
  446. # def det_ext_and_local_ids(self, comm_type: str, prob_init_local: int, prob_rspnd_local: int):
  447. # """
  448. # Map the given IDs to a locality (i.e. local or external} considering the given probabilities.
  449. # :param comm_type: the type of communication (i.e. local, external or mixed)
  450. # :param prob_init_local: the probabilty that an initiator ID is local
  451. # :param prob_rspnd_local: the probabilty that a responder is local
  452. # """
  453. # init_ids, respnd_ids, both_ids = self.init_ids, self.respnd_ids, self.both_ids
  454. # id_comms = sorted(self.id_comms)
  455. # external_ids = set()
  456. # local_ids = set()
  457. # ids = self.init_ids
  458. # def map_init_is_local(id_:str):
  459. # """
  460. # Map the given ID as local and handle its communication partners' locality
  461. # """
  462. # # loop over all communication entries
  463. # for id_comm in id_comms:
  464. # ids = id_comm.split("-")
  465. # other = ids[0] if id_ == ids[1] else ids[1]
  466. # # if id_comm does not contain the ID to be mapped, continue
  467. # if not (id_ == ids[0] or id_ == ids[1]):
  468. # continue
  469. # # if other is already mapped, continue
  470. # if other in local_ids or other in external_ids:
  471. # continue
  472. # # if comm_type is mixed, other ID can be local or external
  473. # if comm_type == "mixed":
  474. # other_pos = mixed_respnd_is_local.random()
  475. # if other_pos == "local":
  476. # local_ids.add(other)
  477. # elif other_pos == "external":
  478. # external_ids.add(other)
  479. # # if comm_type is external, other ID must be external to fulfill type
  480. # # exlude initiators not to throw away too much communication
  481. # elif comm_type == "external":
  482. # if not other in initiators:
  483. # external_ids.add(other)
  484. # def map_init_is_external(id_: int):
  485. # """
  486. # Map the given ID as external and handle its communication partners' locality
  487. # """
  488. # for id_comm in id_comms:
  489. # ids = id_comm.split("-")
  490. # other = ids[0] if id_ == ids[1] else ids[1]
  491. # # if id_comm does not contain the ID to be mapped, continue
  492. # if not (id_ == ids[0] or id_ == ids[1]):
  493. # continue
  494. # # if other is already mapped, continue
  495. # if other in local_ids or other in external_ids:
  496. # continue
  497. # if not other in initiators:
  498. # local_ids.add(other)
  499. # # if comm_type is local, map all IDs to local
  500. # if comm_type == "local":
  501. # local_ids = set(mapped_ids.keys())
  502. # else:
  503. # # set up probabilistic chooser
  504. # init_local_or_external = Lea.fromValFreqsDict({"local": prob_init_local*100, "external": (1-prob_init_local)*100})
  505. # mixed_respnd_is_local = Lea.fromValFreqsDict({"local": prob_rspnd_local*100, "external": (1-prob_rspnd_local)*100})
  506. # # assign IDs in 'both' local everytime for mixed?
  507. # # sort initiators by some order, to gain determinism
  508. # initiators = sorted(list(init_ids) + list(both_ids))
  509. # # sort by individual communication count to increase final communication count
  510. # # better to sort by highest count of 'shared' IDs in case of local comm_type?
  511. # initiators = sorted(initiators, key=lambda id_:self.indv_id_counts[id_], reverse=True)
  512. # for id_ in initiators:
  513. # pos = init_local_or_external.random()
  514. # if pos == "local":
  515. # # if id_ has already been mapped differently, its communication partners still have to be mapped
  516. # if id_ in external_ids:
  517. # map_init_is_external(id_)
  518. # # otherwise, map as chosen above
  519. # else:
  520. # local_ids.add(id_)
  521. # map_init_is_local(id_)
  522. # elif pos == "external":
  523. # # if id_ has already been mapped differently, its communication partners still have to be mapped
  524. # if id_ in local_ids:
  525. # map_init_is_local(id_)
  526. # # otherwise, map as chosen above
  527. # else:
  528. # external_ids.add(id_)
  529. # map_init_is_external(id_)
  530. # self.local_ids, self.external_ids = local_ids, external_ids
  531. # return