pystate.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. from typing import Any, List, Tuple, Optional
  2. from state.base import State, Node, Edge, Element
  3. class PyState(State):
  4. """
  5. State interface implemented using Python data structures.
  6. This code is based on:
  7. https://msdl.uantwerpen.be/git/yentl/modelverse/src/master/state/modelverse_state/main.py
  8. """
  9. def __init__(self):
  10. self.edges = {}
  11. self.outgoing = {}
  12. self.incoming = {}
  13. self.values = {}
  14. self.nodes = set()
  15. # Set used for garbage collection
  16. self.GC = True
  17. self.to_delete = set()
  18. self.cache = {}
  19. self.cache_node = {}
  20. self.cache_all = {}
  21. self.root = self.create_node()
  22. def create_node(self) -> Node:
  23. new_id = self.new_id()
  24. self.nodes.add(new_id)
  25. return new_id
  26. def create_edge(self, source: Element, target: Element) -> Optional[Edge]:
  27. if source not in self.edges and source not in self.nodes:
  28. return None
  29. elif target not in self.edges and target not in self.nodes:
  30. return None
  31. else:
  32. new_id = self.new_id()
  33. self.outgoing.setdefault(source, set()).add(new_id)
  34. self.incoming.setdefault(target, set()).add(new_id)
  35. self.edges[new_id] = (source, target)
  36. if source in self.edges:
  37. # We are creating something dict_readable
  38. # Fill in the cache already!
  39. dict_source, dict_target = self.edges[source]
  40. if target in self.values:
  41. self.cache.setdefault(dict_source, {})[self.values[target]] = source
  42. self.cache_all.setdefault(dict_source, {}).setdefault(self.values[target], set()).add(source)
  43. self.cache_node.setdefault(dict_source, {})[target] = source
  44. return new_id
  45. def create_nodevalue(self, value: Any) -> Optional[Node]:
  46. if not self.is_valid_datavalue(value):
  47. return None
  48. new_id = self.new_id()
  49. self.values[new_id] = value
  50. self.nodes.add(new_id)
  51. return new_id
  52. def create_dict(self, source: Element, value: Any, target: Element) -> None:
  53. if source not in self.nodes and source not in self.edges:
  54. return None
  55. elif target not in self.nodes and target not in self.edges:
  56. return None
  57. elif not self.is_valid_datavalue(value):
  58. return None
  59. else:
  60. n = self.create_nodevalue(value)
  61. e = self.create_edge(source, target)
  62. assert n != None and e != None
  63. e2 = self.create_edge(e, n)
  64. self.cache.setdefault(source, {})[value] = e
  65. self.cache_all.setdefault(source, {}).setdefault(value, set()).add(e)
  66. self.cache_node.setdefault(source, {})[n] = e
  67. def read_root(self) -> Node:
  68. return self.root
  69. def read_value(self, node: Node) -> Any:
  70. if node in self.values:
  71. return self.values[node]
  72. else:
  73. return None
  74. def read_outgoing(self, elem: Element) -> Optional[List[Edge]]:
  75. if elem in self.edges or elem in self.nodes:
  76. if elem in self.outgoing:
  77. return list(self.outgoing[elem])
  78. else:
  79. return []
  80. else:
  81. return None
  82. def read_incoming(self, elem: Element) -> Optional[List[Edge]]:
  83. if elem in self.edges or elem in self.nodes:
  84. if elem in self.incoming:
  85. return list(self.incoming[elem])
  86. else:
  87. return []
  88. else:
  89. return None
  90. def read_edge(self, edge: Edge) -> Tuple[Optional[Element], Optional[Element]]:
  91. if edge in self.edges:
  92. return self.edges[edge][0], self.edges[edge][1]
  93. else:
  94. return None, None
  95. def is_edge(self, elem: Element) -> bool:
  96. return elem in self.edges
  97. def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
  98. e = self.read_dict_edge(elem, value)
  99. if e == None:
  100. return None
  101. else:
  102. return self.edges[e][1]
  103. def read_dict_keys(self, elem: Element) -> Optional[List[Element]]:
  104. if elem not in self.nodes and elem not in self.edges:
  105. return None
  106. result = []
  107. # NOTE: cannot just use the cache here, as some keys in the cache might not actually exist;
  108. # we would have to check all of them anyway
  109. if elem in self.outgoing:
  110. for e1 in self.outgoing[elem]:
  111. if e1 in self.outgoing:
  112. for e2 in self.outgoing[e1]:
  113. result.append(self.edges[e2][1])
  114. return result
  115. def read_dict_edge(self, elem: Element, value: Any) -> Optional[Edge]:
  116. try:
  117. first = self.cache[elem][value]
  118. # Got hit, so validate
  119. if (self.edges[first][0] == elem) and (value in [self.values[self.edges[i][1]]
  120. for i in self.outgoing[first]
  121. if self.edges[i][1] in self.values]):
  122. return first
  123. # Hit but invalid now
  124. del self.cache[elem][value]
  125. self.cache_all[elem][value].remove(first)
  126. return None
  127. except KeyError:
  128. return None
  129. def read_dict_edge_all(self, elem: Element, value: Any) -> List[Edge]:
  130. result = []
  131. try:
  132. all_ = self.cache_all[elem][value]
  133. for a in all_:
  134. try:
  135. if ((self.edges[a][0] == elem) and (value in [self.values[self.edges[i][1]]
  136. for i in self.outgoing[a]
  137. if self.edges[i][1] in self.values])):
  138. result.append(a)
  139. continue
  140. except KeyError:
  141. pass
  142. if len(result) != len(all_):
  143. self.cache_all[elem][value] = set(result)
  144. except KeyError:
  145. pass
  146. return result
  147. def read_dict_node(self, elem: Element, value_node: Node) -> Optional[Element]:
  148. e = self.read_dict_node_edge(elem, value_node)
  149. if e == None:
  150. return None
  151. else:
  152. self.cache_node.setdefault(elem, {})[value_node] = e
  153. return self.edges[e][1]
  154. def read_dict_node_edge(self, elem: Element, value_node: Node) -> Optional[Edge]:
  155. try:
  156. first = self.cache_node[elem][value_node]
  157. # Got hit, so validate
  158. if (self.edges[first][0] == elem) and \
  159. (value_node in [self.edges[i][1] for i in self.outgoing[first]]):
  160. return first
  161. # Hit but invalid now
  162. del self.cache_node[elem][value_node]
  163. return None
  164. except KeyError:
  165. return None
  166. def read_reverse_dict(self, elem: Element, value: Any) -> Optional[List[Element]]:
  167. if elem not in self.nodes and elem not in self.edges:
  168. return None
  169. # Get all outgoing links
  170. matches = []
  171. if elem in self.incoming:
  172. for e1 in self.incoming[elem]:
  173. # For each link, we read the links that might link to a data value
  174. if e1 in self.outgoing:
  175. for e2 in self.outgoing[e1]:
  176. # Now read out the target of the link
  177. target = self.edges[e2][1]
  178. # And access its value
  179. if target in self.values and self.values[target] == value:
  180. # Found a match
  181. matches.append(e1)
  182. return [self.edges[e][0] for e in matches]
  183. def delete_node(self, node: Node) -> None:
  184. if node == self.root:
  185. return
  186. elif node not in self.nodes:
  187. return
  188. self.nodes.remove(node)
  189. if node in self.values:
  190. del self.values[node]
  191. s = set()
  192. if node in self.outgoing:
  193. for e in self.outgoing[node]:
  194. s.add(e)
  195. del self.outgoing[node]
  196. if node in self.incoming:
  197. for e in self.incoming[node]:
  198. s.add(e)
  199. del self.incoming[node]
  200. for e in s:
  201. self.delete_edge(e)
  202. if node in self.outgoing:
  203. del self.outgoing[node]
  204. if node in self.incoming:
  205. del self.incoming[node]
  206. def delete_edge(self, edge: Edge) -> None:
  207. if edge not in self.edges:
  208. return
  209. s, t = self.edges[edge]
  210. if t in self.incoming:
  211. self.incoming[t].remove(edge)
  212. if s in self.outgoing:
  213. self.outgoing[s].remove(edge)
  214. del self.edges[edge]
  215. s = set()
  216. if edge in self.outgoing:
  217. for e in self.outgoing[edge]:
  218. s.add(e)
  219. if edge in self.incoming:
  220. for e in self.incoming[edge]:
  221. s.add(e)
  222. for e in s:
  223. self.delete_edge(e)
  224. if edge in self.outgoing:
  225. del self.outgoing[edge]
  226. if edge in self.incoming:
  227. del self.incoming[edge]
  228. if self.GC and (t in self.incoming and not self.incoming[t]) and (t not in self.edges):
  229. # Remove this node as well
  230. # Edges aren't deleted like this, as they might have a reachable target and source!
  231. # If they haven't, they will be removed because the source was removed.
  232. self.to_delete.add(t)
  233. def purge(self):
  234. while self.to_delete:
  235. t = self.to_delete.pop()
  236. if t in self.incoming and not self.incoming[t]:
  237. self.delete_node(t)
  238. values = set(self.edges)
  239. values.update(self.nodes)
  240. visit_list = [self.root]
  241. while visit_list:
  242. elem = visit_list.pop()
  243. if elem in values:
  244. # Remove it from the leftover values
  245. values.remove(elem)
  246. if elem in self.edges:
  247. visit_list.extend(self.edges[elem])
  248. if elem in self.outgoing:
  249. visit_list.extend(self.outgoing[elem])
  250. if elem in self.incoming:
  251. visit_list.extend(self.incoming[elem])
  252. dset = set()
  253. for key in self.cache:
  254. if key not in self.nodes and key not in self.edges:
  255. dset.add(key)
  256. for key in dset:
  257. del self.cache[key]
  258. del self.cache_all[key]
  259. dset = set()
  260. for key in self.cache_node:
  261. if key not in self.nodes and key not in self.edges:
  262. dset.add(key)
  263. for key in dset:
  264. del self.cache_node[key]
  265. # All remaining elements are to be purged
  266. if len(values) > 0:
  267. while values:
  268. v = values.pop()
  269. if v in self.nodes:
  270. self.delete_node(v)