pystate.py 9.7 KB

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