memory-graph 0.2.4__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {memory_graph-0.2.4 → memory_graph-0.3.1}/PKG-INFO +3 -3
  2. {memory_graph-0.2.4 → memory_graph-0.3.1}/README.md +2 -2
  3. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph/__init__.py +17 -10
  4. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph/config.py +4 -3
  5. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph/config_default.py +21 -25
  6. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph/config_helpers.py +10 -28
  7. memory_graph-0.3.1/memory_graph/extension_numpy.py +26 -0
  8. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph/extension_pandas.py +3 -3
  9. memory_graph-0.2.4/memory_graph/HTML_Table.py → memory_graph-0.3.1/memory_graph/html_table.py +14 -8
  10. memory_graph-0.3.1/memory_graph/list_view.py +53 -0
  11. memory_graph-0.3.1/memory_graph/memory_to_nodes.py +186 -0
  12. memory_graph-0.3.1/memory_graph/node_base.py +126 -0
  13. memory_graph-0.3.1/memory_graph/node_key_value.py +118 -0
  14. memory_graph-0.3.1/memory_graph/node_linear.py +94 -0
  15. memory_graph-0.3.1/memory_graph/node_table.py +79 -0
  16. memory_graph-0.3.1/memory_graph/sequence.py +107 -0
  17. memory_graph-0.3.1/memory_graph/slicer.py +42 -0
  18. memory_graph-0.3.1/memory_graph/slices.py +159 -0
  19. memory_graph-0.3.1/memory_graph/slices_iterator.py +51 -0
  20. memory_graph-0.3.1/memory_graph/slices_table_iterator.py +75 -0
  21. memory_graph-0.3.1/memory_graph/t.py +15 -0
  22. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph/test.py +83 -20
  23. memory_graph-0.3.1/memory_graph/test_memory_graph.py +11 -0
  24. memory_graph-0.3.1/memory_graph/test_memory_to_nodes.py +11 -0
  25. memory_graph-0.3.1/memory_graph/test_sequence.py +33 -0
  26. memory_graph-0.3.1/memory_graph/test_slicer.py +46 -0
  27. memory_graph-0.3.1/memory_graph/test_slices.py +87 -0
  28. memory_graph-0.3.1/memory_graph/test_slices_iterator.py +24 -0
  29. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph/utils.py +26 -4
  30. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph.egg-info/PKG-INFO +3 -3
  31. memory_graph-0.3.1/memory_graph.egg-info/SOURCES.txt +36 -0
  32. {memory_graph-0.2.4 → memory_graph-0.3.1}/setup.py +1 -1
  33. memory_graph-0.2.4/memory_graph/Memory_Graph.py +0 -76
  34. memory_graph-0.2.4/memory_graph/Memory_Visitor.py +0 -81
  35. memory_graph-0.2.4/memory_graph/Node.py +0 -111
  36. memory_graph-0.2.4/memory_graph/Node_Hidden.py +0 -26
  37. memory_graph-0.2.4/memory_graph/Node_Key_Value.py +0 -103
  38. memory_graph-0.2.4/memory_graph/Node_Linear.py +0 -66
  39. memory_graph-0.2.4/memory_graph/Node_Table.py +0 -90
  40. memory_graph-0.2.4/memory_graph/Sliced.py +0 -114
  41. memory_graph-0.2.4/memory_graph/Slicer.py +0 -169
  42. memory_graph-0.2.4/memory_graph/extension_numpy.py +0 -44
  43. memory_graph-0.2.4/memory_graph/special_types.py +0 -4
  44. memory_graph-0.2.4/memory_graph/test_memory_graph.py +0 -13
  45. memory_graph-0.2.4/memory_graph/test_memory_visitor.py +0 -10
  46. memory_graph-0.2.4/memory_graph.egg-info/SOURCES.txt +0 -30
  47. {memory_graph-0.2.4 → memory_graph-0.3.1}/LICENSE.txt +0 -0
  48. {memory_graph-0.2.4 → memory_graph-0.3.1}/MANIFEST.in +0 -0
  49. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph.egg-info/dependency_links.txt +0 -0
  50. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph.egg-info/requires.txt +0 -0
  51. {memory_graph-0.2.4 → memory_graph-0.3.1}/memory_graph.egg-info/top_level.txt +0 -0
  52. {memory_graph-0.2.4 → memory_graph-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: memory_graph
3
- Version: 0.2.4
3
+ Version: 0.3.1
4
4
  Summary: Draws a graph of your data to analyze its structure.
5
5
  Home-page: https://github.com/bterwijn/memory_graph
6
6
  Author: Bas Terwijn
@@ -131,7 +131,7 @@ The [Python Data Model](https://docs.python.org/3/reference/datamodel.html) make
131
131
  In the code below variable `a` and `b` both reference the same `int` value 10. An `int` is an immutable type and therefore when we change variable `a` its value can **not** be mutated in place, and thus a copy is made and `a` and `b` reference a different value afterwards.
132
132
  ```python
133
133
  import memory_graph
134
- memory_graph.config.no_reference_types.pop(int, None) # show references to ints
134
+ memory_graph.config.not_node_types.remove(int) # show references to ints
135
135
 
136
136
  a = 10
137
137
  b = a
@@ -302,7 +302,7 @@ def factorial(n):
302
302
  memory_graph.show( memory_graph.get_call_stack(), block=True )
303
303
  return result
304
304
 
305
- factorial(3)
305
+ print(factorial(3))
306
306
  ```
307
307
  ![factorial.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial.gif)
308
308
 
@@ -112,7 +112,7 @@ The [Python Data Model](https://docs.python.org/3/reference/datamodel.html) make
112
112
  In the code below variable `a` and `b` both reference the same `int` value 10. An `int` is an immutable type and therefore when we change variable `a` its value can **not** be mutated in place, and thus a copy is made and `a` and `b` reference a different value afterwards.
113
113
  ```python
114
114
  import memory_graph
115
- memory_graph.config.no_reference_types.pop(int, None) # show references to ints
115
+ memory_graph.config.not_node_types.remove(int) # show references to ints
116
116
 
117
117
  a = 10
118
118
  b = a
@@ -283,7 +283,7 @@ def factorial(n):
283
283
  memory_graph.show( memory_graph.get_call_stack(), block=True )
284
284
  return result
285
285
 
286
- factorial(3)
286
+ print(factorial(3))
287
287
  ```
288
288
  ![factorial.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial.gif)
289
289
 
@@ -1,12 +1,15 @@
1
- from memory_graph.Memory_Graph import Memory_Graph
2
-
3
- import memory_graph.config_default as config_default
1
+ import memory_graph.memory_to_nodes as memory_to_nodes
2
+ import memory_graph.config as config
3
+ import memory_graph.config_default
4
+ import memory_graph.config_helpers as config_helper
4
5
  import memory_graph.utils as utils
5
6
 
6
7
  import inspect
7
8
  import sys
8
9
 
9
- __version__ = "0.2.04"
10
+ import graphviz
11
+
12
+ __version__ = "0.3.01"
10
13
  __author__ = 'Bas Terwijn'
11
14
 
12
15
  log_file=sys.stdout
@@ -30,27 +33,31 @@ def create_graph(data,
30
33
  vertical_orientations = None,
31
34
  slicers = None):
32
35
  """ Creates and returns a memory graph from 'data'. """
33
- memory_graph = Memory_Graph(data, colors, vertical_orientations, slicers)
34
- return memory_graph.get_graph()
36
+ config_helper.set_config(colors, vertical_orientations, slicers)
37
+ graphviz_graph = memory_to_nodes.memory_to_nodes(data)
38
+ return graphviz_graph
35
39
 
36
- def show(data,block=False,
40
+ def show(data=None ,block=False, stack_index=2,
37
41
  colors = None,
38
42
  vertical_orientations = None,
39
43
  slicers = None):
40
44
  """ Shows the graph of 'data' and optionally blocks. """
45
+ if data is None:
46
+ data=get_locals_from_calling_frame(stack_index)
41
47
  graph = create_graph(data, colors, vertical_orientations, slicers)
42
- #print('graph:',graph)
43
48
  graph.view()
44
49
  if block:
45
50
  input(f"showing '{graph.filename}', {get_source_location(2)}, {press_enter_text}")
46
51
 
47
- def render(data, output_filename=None, block=False,
52
+ def render(data=None, outfile=None, block=False, stack_index=2,
48
53
  colors = None,
49
54
  vertical_orientations = None,
50
55
  slicers = None):
51
56
  """ Renders the graph of 'data' to 'output_filename' and optionally blocks. """
57
+ if data is None:
58
+ data=get_locals_from_calling_frame(stack_index)
52
59
  graph = create_graph(data, colors, vertical_orientations, slicers)
53
- filename = output_filename if output_filename else graph.filename
60
+ filename = outfile if outfile else graph.filename+".pdf"
54
61
  graph.render(outfile=filename)
55
62
  if block:
56
63
  input(f"rendering '{filename}', {get_source_location(2)}, {press_enter_text}")
@@ -1,9 +1,10 @@
1
1
  """ Configuration file for the graph visualizer. The configuration values are set later by the 'config_default.py' file. """
2
2
 
3
- max_number_nodes = 0
4
- max_string_length = 0
3
+ max_tree_depth = None
4
+ max_missing_edges = None
5
+ max_string_length = None
5
6
 
6
- no_reference_types = {}
7
+ not_node_types = {}
7
8
  no_child_references_types = set()
8
9
 
9
10
  type_to_node = { }
@@ -1,49 +1,45 @@
1
1
  """ Sets the default configuration values for the memory graph. """
2
- from memory_graph.Node import Node
3
- from memory_graph.Node_Linear import Node_Linear
4
- from memory_graph.Node_Key_Value import Node_Key_Value
5
- from memory_graph.Node_Hidden import Node_Hidden
6
- from memory_graph.Node_Table import Node_Table
7
- from memory_graph.Slicer import Slicer
2
+ from memory_graph.node_base import Node_Base
3
+ from memory_graph.node_linear import Node_Linear
4
+ from memory_graph.node_key_value import Node_Key_Value
5
+ from memory_graph.node_table import Node_Table
6
+
7
+ from memory_graph.slicer import Slicer
8
8
 
9
9
  import memory_graph.config as config
10
10
  import memory_graph.utils as utils
11
11
 
12
12
  import types
13
13
 
14
- """ The maxium number of Nodes shown in the graph. When the graph gets too big set this to a smaller number to analyze the problem. A `★` symbol indictes where the gra[h is cut short. """
15
- config.max_number_nodes = 1000
14
+ """ The maximum depth of nodes in the graph. When the graph gets too big set this to a small positive number. A `★` symbol indictes where the graph is cut short. """
15
+ config.max_tree_depth = -1
16
+
17
+ config.max_missing_edges = 3
16
18
 
17
19
  """ The maximum length of strings shown in the graph. Longer strings will be truncated. """
18
20
  config.max_string_length = 42
19
21
 
20
22
  """ Types that by default will not have references pointing to them in the graph but instead will be visualized in the node of their parent. """
21
- config.no_reference_types = {
22
- type(None) : lambda d: "None", # so None can be used to indicate no value
23
- bool : lambda d: d,
24
- int : lambda d: d,
25
- float : lambda d: d,
26
- complex : lambda d: d,
27
- str : lambda d: d,
28
- types.FunctionType : lambda d: str(d.__qualname__),
29
- types.MethodType : lambda d: str(d.__qualname__),
23
+ config.not_node_types = {
24
+ type(None), bool, int, float, complex, str,
25
+ types.FunctionType,
26
+ types.MethodType,
30
27
  }
31
28
 
32
29
  """ Types that will not have references pointing to their children in the graph but instead will have their children visualized in their node. """
33
30
  config.no_child_references_types = {dict, types.MappingProxyType}
34
31
 
35
- """ Conversion from type to Node objects. """
32
+ """ Conversion from type to Node_Base objects. """
36
33
  config.type_to_node = {
37
- str: lambda data: Node(data), # visit as whole string, don't iterate over characters
34
+ str: lambda data: Node_Base(data), # visit as whole string, don't iterate over characters
35
+ types.FunctionType: lambda data: Node_Base(data.__qualname__),
36
+ types.MethodType: lambda data: Node_Base(data.__qualname__),
38
37
  range: lambda data: Node_Key_Value(data, {'start':data.start, 'stop':data.stop, 'step':data.step}.items()),
39
- types.FunctionType: lambda data: Node(data.__qualname__),
40
- types.MethodType: lambda data: Node(data.__qualname__),
41
38
  dict: lambda data: (
42
- Node_Key_Value(data, utils.filter_dict_attributes(data.items()) )
39
+ Node_Key_Value(data, utils.filter_dict(data.items()) )
43
40
  if dict in config.no_child_references_types else
44
- Node_Linear(data, utils.filter_dict_attributes(data.items()) )
41
+ Node_Linear(data, utils.filter_dict(data.items()) )
45
42
  ),
46
- Node_Hidden: lambda data: data,
47
43
  }
48
44
 
49
45
  """ Colors of different types in the graph. """
@@ -71,7 +67,7 @@ config.type_to_color = {
71
67
  }
72
68
 
73
69
  """ Types that will be visualized in vertical orientation if 'True', or horizontal orientation
74
- if 'False'. Otherwise the Node decides based on it having references."""
70
+ if 'False'. Otherwise the Node_Base decides based on it having references."""
75
71
  config.type_to_vertical_orientation = {
76
72
  }
77
73
 
@@ -1,8 +1,7 @@
1
1
  """ This module provides helper functions to access the configuration of the memory graph. """
2
- from memory_graph.Slicer import Slicer
2
+ from memory_graph.slicer import Slicer
3
3
 
4
4
  import memory_graph.config as config
5
- import memory_graph.utils as utils
6
5
 
7
6
  type_to_color = None
8
7
  type_to_vertical_orientation = None
@@ -32,39 +31,22 @@ def get_property(data_id, data_type, node_type, dictionary, default):
32
31
  return default
33
32
 
34
33
  def get_color(node, default='white'):
35
- return get_property(id(node.get_data()),
36
- type(node.get_data()),
37
- type(node),
34
+ return get_property(node.get_id(),
35
+ node.get_type(),
36
+ type(node),
38
37
  type_to_color,
39
38
  default)
40
39
 
41
40
  def get_vertical_orientation(node, default):
42
- return get_property(id(node.get_data()),
43
- type(node.get_data()),
44
- type(node),
41
+ return get_property(node.get_id(),
42
+ node.get_type(),
43
+ type(node),
45
44
  type_to_vertical_orientation,
46
45
  default)
47
46
 
48
- def get_slicer_1d(node, data, default=Slicer(10,5,10)):
49
- slicer = get_property(id(data),
50
- type(data),
51
- type(node),
52
- type_to_slicer,
53
- default)
54
- if type(slicer) is Slicer:
55
- return slicer
56
- if utils.is_iterable(slicer):
57
- return next(iter(slicer))
58
- return default
59
-
60
- def get_slicer_2d(node, data, default=Slicer(5,5)):
61
- slicer = get_property(id(data),
47
+ def get_slicer(node, data, default=Slicer(3,2,3)):
48
+ return get_property(id(data),
62
49
  type(data),
63
50
  type(node),
64
51
  type_to_slicer,
65
- default)
66
- if type(slicer) is Slicer:
67
- return slicer, slicer
68
- if len(slicer) == 2:
69
- return slicer
70
- return default, default
52
+ default)
@@ -0,0 +1,26 @@
1
+ """ Extension to add the memory graph configuration for Numpy types. """
2
+ from memory_graph.node_linear import Node_Linear
3
+ from memory_graph.node_table import Node_Table
4
+
5
+ import memory_graph.config as config
6
+
7
+ import numpy as np
8
+
9
+ config.not_node_types |= {
10
+ np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64,
11
+ np.float16, np.float32, np.float64, np.float_,
12
+ np.complex64, np.complex128, np.complex_,
13
+ np.bool_, np.bytes_, np.str_, np.datetime64, np.timedelta64
14
+ }
15
+
16
+ def ndarrayy_to_node(ndarrayy_data):
17
+ if len(ndarrayy_data.shape) == 2:
18
+ return Node_Table(ndarrayy_data, ndarrayy_data)
19
+ else:
20
+ return Node_Linear(ndarrayy_data, ndarrayy_data)
21
+
22
+ config.type_to_node[np.matrix] = lambda data : Node_Table(data, np.asarray(data)) # convert to ndarray to avoid infinite recursion due to index issue
23
+ config.type_to_node[np.ndarray] = lambda data : ndarrayy_to_node(data)
24
+
25
+ config.type_to_color[np.ndarray] = "hotpink1"
26
+ config.type_to_color[np.matrix] = "hotpink2"
@@ -1,6 +1,6 @@
1
1
  """ Extension to add the memory graph configuration for Pandas type. """
2
- from memory_graph.Node_Linear import Node_Linear
3
- from memory_graph.Node_Table import Node_Table
2
+ from memory_graph.node_linear import Node_Linear
3
+ from memory_graph.node_table import Node_Table
4
4
 
5
5
  import memory_graph.config as config
6
6
 
@@ -9,7 +9,7 @@ import pandas as pd
9
9
  config.type_to_node[pd.DataFrame] = lambda data : (
10
10
  Node_Table(data,
11
11
  data.values.tolist(),
12
- column_names=data.columns.tolist(),
12
+ col_names = data.columns.tolist(),
13
13
  row_names = [ str(i) for i in data.index.tolist()]
14
14
  )
15
15
  )
@@ -1,4 +1,5 @@
1
- from memory_graph.Node import Node
1
+ from memory_graph.node_base import Node_Base
2
+ import memory_graph.node_base
2
3
 
3
4
  import memory_graph.config as config
4
5
 
@@ -59,7 +60,6 @@ class HTML_Table:
59
60
  def add_string(self, s):
60
61
  """ Add a string s to the outer table. """
61
62
  self.html += format_string(s)
62
- #self.col_count += 1
63
63
 
64
64
  def add_index(self, s):
65
65
  """ Add an index s to the inner table. """
@@ -67,10 +67,16 @@ class HTML_Table:
67
67
  self.html += f'<TD><font color="#505050">{str(s)}</font></TD>'
68
68
  self.col_count += 1
69
69
 
70
- def add_entry(self, node, child, rounded=False, border=1):
71
- """ Add child to the inner table either as reference if it a Node or as a value otherwise. """
72
- if isinstance(child, Node):
73
- self.add_reference(node, child, rounded, border)
70
+ def add_entry(self, node, nodes, child, id_to_slices, rounded=False, border=1, dashed=False):
71
+ """ Add child to the inner table either as reference if it is a Node_Base or as a value otherwise. """
72
+ #print('child:', child)
73
+ child_id = id(child)
74
+ if child_id in nodes:
75
+ child = nodes[child_id]
76
+ if child_id in id_to_slices:
77
+ self.add_reference(node, child, rounded, border, dashed)
78
+ else:
79
+ self.add_value("✂", rounded, border)
74
80
  else:
75
81
  self.add_value(child, rounded, border)
76
82
 
@@ -81,13 +87,13 @@ class HTML_Table:
81
87
  self.html += f'<TD BORDER="{border}"{r}> {format_string(s)} </TD>'
82
88
  self.col_count += 1
83
89
 
84
- def add_reference(self, node, child, rounded=False, border=1):
90
+ def add_reference(self, node, child, rounded=False, border=1, dashed=False):
85
91
  """ Helper function to add a reference to the inner table. """
86
92
  self.check_add_new_line()
87
93
  r = ' STYLE="ROUNDED"' if rounded else ''
88
94
  self.html += f'<TD BORDER="{border}" PORT="ref{self.ref_count}"{r}> </TD>'
89
95
  self.edges.append( (f'{node.get_name()}:ref{self.ref_count}',
90
- child.get_name()) )
96
+ child.get_name(), dashed) )
91
97
  self.ref_count += 1
92
98
  self.col_count += 1
93
99
 
@@ -0,0 +1,53 @@
1
+
2
+ class List_View:
3
+ def __init__(self, base_list, begin, end):
4
+ self.base_list = base_list
5
+ self.begin = max(0, begin)
6
+ self.end = min(end, len(base_list))
7
+
8
+ def __getitem__(self, index):
9
+ if isinstance(index, slice):
10
+ # Calculate new begin and end indices within the bounds of the current view
11
+ start, stop, step = index.indices(self.end - self.begin)
12
+ if step != 1:
13
+ raise ValueError("List_View does not support slices with steps other than 1")
14
+ # Adjust the indices relative to the base list
15
+ new_start = self.begin + start
16
+ new_end = self.begin + stop
17
+ return List_View(self.base_list, new_start, new_end)
18
+ elif isinstance(index, int):
19
+ if index < 0 or index >= (self.end - self.begin):
20
+ raise IndexError("list index out of range")
21
+ return self.base_list[self.begin + index]
22
+ else:
23
+ raise TypeError("Invalid index type")
24
+
25
+ def __setitem__(self, index, value):
26
+ if index < 0 or index >= (self.end - self.begin):
27
+ raise IndexError("list index out of range")
28
+ self.base_list[self.begin + index] = value
29
+
30
+ def __len__(self):
31
+ return self.end - self.begin
32
+
33
+ def __iter__(self):
34
+ for i in range(self.begin, self.end):
35
+ yield self.base_list[i]
36
+
37
+ def __repr__(self):
38
+ return f"List_View({self.base_list[self.begin:self.end]})"
39
+
40
+ def test_list_vew():
41
+ original_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
42
+ list_view = List_View(original_list, 3, 8)
43
+ print(list_view) # Output: List_View([3, 4, 5, 6, 7])
44
+ print(list_view[1:4]) # Output: List_View([4, 5, 6])
45
+ # 2D array
46
+ n = 4
47
+ data = [i for i in range(n*n)]
48
+ list_views = [List_View(data, i, i+n) for i in range(0,len(data),n)]
49
+ for row in list_views:
50
+ print(row)
51
+
52
+ if __name__ == "__main__":
53
+ test_list_vew()
@@ -0,0 +1,186 @@
1
+ from memory_graph.node_base import Node_Base
2
+ from memory_graph.node_linear import Node_Linear
3
+ from memory_graph.node_key_value import Node_Key_Value
4
+
5
+ import memory_graph.utils as utils
6
+ import memory_graph.config as config
7
+
8
+ import graphviz
9
+
10
+ def read_nodes(data):
11
+
12
+ def data_to_node(data_type, data):
13
+ if data_type in config.type_to_node: # for predefined types
14
+ return config.type_to_node[data_type](data)
15
+ elif utils.has_dict_attributes(data): # for user defined classes
16
+ return Node_Key_Value(data, utils.filter_dict_attributes(utils.get_dict_attributes(data)) )
17
+ elif utils.is_iterable(data): # for lists, tuples, sets, ...
18
+ return Node_Linear(data, data)
19
+ else:
20
+ return Node_Base(data)
21
+
22
+ def memory_to_nodes_recursive(nodes, data, parent, parent_index):
23
+ data_type = type(data)
24
+ if not data_type in config.not_node_types or parent is None:
25
+ data_id = id(data)
26
+ if data_id in nodes:
27
+ node = nodes[data_id]
28
+ else:
29
+ node = data_to_node(data_type, data)
30
+ nodes[data_id] = node
31
+ for index in node.get_children().indices_all():
32
+ child = node.get_children()[index]
33
+ memory_to_nodes_recursive(nodes, child, node, index)
34
+ node.add_parent_index(parent, parent_index)
35
+
36
+ nodes = {}
37
+ memory_to_nodes_recursive(nodes, data, None, None)
38
+ root_id = id(data)
39
+ return nodes, root_id
40
+
41
+ # --------------------------------------------------------------------------------------------
42
+
43
+ def slice_nodes(nodes, root_id, max_tree_depth):
44
+
45
+ def slice_nodes_recursive(nodes, node_id, id_to_slices, max_tree_depth):
46
+ if max_tree_depth == 0 or node_id in id_to_slices:
47
+ return
48
+ if node_id in nodes:
49
+ node = nodes[node_id]
50
+ children = node.get_children()
51
+ if children.is_empty():
52
+ id_to_slices[node_id] = None
53
+ else:
54
+ slicer = node.get_slicer()
55
+ slices = children.slice(slicer)
56
+ id_to_slices[node_id] = slices
57
+ if not node.is_hidden_node():
58
+ max_tree_depth -= 1
59
+ for index in slices:
60
+ slice_nodes_recursive(nodes, id(children[index]), id_to_slices, max_tree_depth)
61
+ id_to_slices = {}
62
+ slice_nodes_recursive(nodes, root_id, id_to_slices, max_tree_depth)
63
+ return id_to_slices
64
+
65
+ # --------------------------------------------------------------------------------------------
66
+
67
+ def add_parent_indices(nodes, type_to_parent_indices, id_to_slices, max_missing_edges):
68
+ #print('add_parent_indices type_to_parent_indices:',type_to_parent_indices)
69
+ for _, parent_indices in type_to_parent_indices.items():
70
+ dashed = len(parent_indices) > max_missing_edges
71
+ for parent, index in parent_indices[0:max_missing_edges]:
72
+ new_parent = False
73
+ parent_id = parent.get_id()
74
+ if not parent_id in id_to_slices:
75
+ new_parent = True
76
+ id_to_slices[parent_id] = parent.get_children().empty_slices()
77
+ slices = id_to_slices[parent_id]
78
+ slices.add_index(index, dashed=dashed)
79
+ if new_parent:
80
+ add_indices_to_parents(nodes, parent_id, id_to_slices, max_missing_edges)
81
+
82
+ def add_indices_to_parents(nodes, node_id, id_to_slices, max_missing_edges):
83
+ #print('add_indices_to_parents node_id:',node_id)
84
+ type_to_parent_indices = {}
85
+ parent_indices = nodes[node_id].get_parent_indices()
86
+ for parent, indices in parent_indices.items():
87
+ if parent is None:
88
+ continue
89
+ parent_type = parent.get_type()
90
+ parent_id = parent.get_id()
91
+ if (parent_type in type_to_parent_indices and
92
+ len(type_to_parent_indices[parent_type]) > max_missing_edges): # early stop
93
+ continue
94
+ parent_slices = None
95
+ if parent_id in id_to_slices:
96
+ parent_slices = id_to_slices[parent_id]
97
+ for index in indices:
98
+ if parent_slices is None or not parent_slices.has_index(index):
99
+ if not parent_type in type_to_parent_indices:
100
+ type_to_parent_indices[parent_type] = []
101
+ parent_indices = type_to_parent_indices[parent_type]
102
+ if len(parent_indices) > max_missing_edges:
103
+ break
104
+ else:
105
+ parent_indices.append((parent, index))
106
+ add_parent_indices(nodes, type_to_parent_indices, id_to_slices, max_missing_edges)
107
+
108
+ def add_missing_edges(nodes, id_to_slices, max_missing_edges=3):
109
+ old_id_to_slices_keys = set(id_to_slices.keys())
110
+ for node_id in old_id_to_slices_keys:
111
+ add_indices_to_parents(nodes, node_id, id_to_slices, max_missing_edges)
112
+ return id_to_slices
113
+
114
+ # --------------------------------------------------------------------------------------------
115
+
116
+ import memory_graph.config_helpers as config_helpers
117
+
118
+ def create_depth_of_nodes(nodes, nodes_at_depth):
119
+ depth_of_nodes = {}
120
+ for node_id, depth in nodes_at_depth.items():
121
+ node = nodes[node_id]
122
+ if node_id in nodes and not node.is_hidden_node():
123
+ if not depth in depth_of_nodes:
124
+ depth_of_nodes[depth] = []
125
+ depth_of_nodes[depth].append(node)
126
+ return depth_of_nodes
127
+
128
+ def add_subgraph(graphviz_graph, nodes_to_subgraph):
129
+ new_node_names = [node.get_name() for node in nodes_to_subgraph]
130
+ if len(new_node_names) > 1:
131
+ #graphviz_graph.body.append('{ rank="same" '+(" -> ".join(new_node_names))+' [weight=999,style=invis]; }\n')
132
+ graphviz_graph.body.append('{ rank="same" '+('; '.join(new_node_names))+'; }\n')
133
+
134
+ def add_to_graphviz_graph(graphviz_graph, nodes, node, slices, id_to_slices, subgraphed_nodes, depth):
135
+ html_table = node.get_html_table(nodes, slices, id_to_slices)
136
+ edges = html_table.get_edges()
137
+ color = config_helpers.get_color(node)
138
+ border = 1 #3 if node.is_root() else 1 # TODO
139
+ graphviz_graph.node(node.get_name(),
140
+ html_table.to_string(border, color),
141
+ xlabel=node.get_label(slices))
142
+ # ------------ edges
143
+ for parent,child,dashed in edges:
144
+ graphviz_graph.edge(parent, child+':table', style='dashed' if dashed else 'solid')
145
+
146
+ def build_graph_depth_first(graphviz_graph, nodes, node_id, id_to_slices, nodes_at_depth, subgraphed_nodes, depth):
147
+ if node_id in id_to_slices:
148
+ if node_id in nodes_at_depth:
149
+ return
150
+ nodes_at_depth[node_id] = depth
151
+ node = nodes[node_id]
152
+ children = node.get_children()
153
+ slices = None
154
+ if node_id in id_to_slices:
155
+ slices = id_to_slices[node_id]
156
+ if not slices is None:
157
+ for index in slices:
158
+ child_id = id(children[index])
159
+ build_graph_depth_first(graphviz_graph, nodes, child_id, id_to_slices, nodes_at_depth, subgraphed_nodes, depth+1)
160
+ if not node.is_hidden_node():
161
+ add_to_graphviz_graph(graphviz_graph, nodes, node, slices, id_to_slices, subgraphed_nodes, depth)
162
+
163
+ def build_graph(graphviz_graph, nodes, root_id, id_to_slices):
164
+ nodes_at_depth = {}
165
+ build_graph_depth_first(graphviz_graph, nodes, root_id, id_to_slices, nodes_at_depth, set(), 0)
166
+ depth_of_nodes = create_depth_of_nodes(nodes, nodes_at_depth)
167
+ #print('nodes_at_depth:',nodes_at_depth,'depth_of_nodes:', depth_of_nodes)
168
+ for depth, depth_nodes in depth_of_nodes.items():
169
+ add_subgraph(graphviz_graph, depth_nodes)
170
+
171
+ def memory_to_nodes(data):
172
+ nodes, root_id = read_nodes(data)
173
+ #print('nodes:',nodes,'root_id:',root_id)
174
+ id_to_slices = slice_nodes(nodes, root_id, config.max_tree_depth)
175
+ #print('id_to_slices:',id_to_slices)
176
+ id_to_slices = add_missing_edges(nodes, id_to_slices, config.max_missing_edges)
177
+ #print('id_to_slices:',id_to_slices)
178
+ graphviz_graph_attr = {}
179
+ graphviz_node_attr = {'shape':'plaintext'}
180
+ graphviz_edge_attr = {}
181
+ graphviz_graph=graphviz.Digraph('memory_graph',
182
+ graph_attr=graphviz_graph_attr,
183
+ node_attr=graphviz_node_attr,
184
+ edge_attr=graphviz_edge_attr)
185
+ build_graph(graphviz_graph, nodes, root_id, id_to_slices)
186
+ return graphviz_graph