utils

Some utilities for minion for visualizations, context managers etc…
g = Digraph("Computational Graph of Value", comment="Comp Graph"); g
a = g.node("A")
b = g.node("B")
# g.node?
a = Value(2.0, label="a")
b = Value(-3.0, label="b")
c = Value(10.0, label="c")

d = a*b; d.label = "d"
e = d+c; e.label = "e"
f = Value(-2.0, label="f")
L = e*f; L.label="L"
L
Value(L|data=-8.0)
for i in e._prev:
    print(i, i._op)
Value(c|data=10.0) None
Value(d|data=-6.0) *
g = Digraph("Computational Graph of Value", comment="Comp Graph"); g
g.node(str(1), "A|data=4")
g.node(str(2), "A|data=9")
g.edges(["12"])
g

# Below doesn't work

# def build_graph(root, g = None):
#     if not g: g = graphviz.Digraph("Computational Graph of Value", comment="Comp Graph"); g
#     g.node("a", f"{root.label}|{root.data}")
#     if root._prev:
#         t = ""
#         for i, val in enumerate(root._prev):
#             g.node(str(i),f"{val.label}|{val.data}" )
#             g.edges([str(i)+"a"])
#             build_graph(val,)
#     return g

# build_graph(e)

In order for us to build a visualization of computational graph of Value object. We need to first create a set of all nodes and connections/ edges between them. We can then use graphviz to visualize the graph in one shot. This would be the simplest algorithm for now


source

trace

 trace (root)

It’s interesting to choose a glocal sets for nodes and edges, calling them implicity within trace inside recursion. It does make for one simpler and efficient algorithm

trace(L)
({Value(L|data=-8.0),
  Value(a|data=2.0),
  Value(b|data=-3.0),
  Value(c|data=10.0),
  Value(d|data=-6.0),
  Value(e|data=4.0),
  Value(f|data=-2.0)},
 {(Value(a|data=2.0), Value(d|data=-6.0)),
  (Value(b|data=-3.0), Value(d|data=-6.0)),
  (Value(c|data=10.0), Value(e|data=4.0)),
  (Value(d|data=-6.0), Value(e|data=4.0)),
  (Value(e|data=4.0), Value(L|data=-8.0)),
  (Value(f|data=-2.0), Value(L|data=-8.0))})

source

draw_dot

 draw_dot (root)
draw_dot(L)

draw_dot(a)

draw_dot(d)