Skip to content

Visualizing Computation Graphs

Loman supports viewing computations as graphs using the open source Graphviz graph visualization software.

By default, in a Jupyter notebook, when a graph is evaluated, it will show a graphical representation of its computation dependencies and its state:

>>> from loman import *

>>> comp = Computation()
>>> comp.add_node('a', value=1)
>>> comp.add_node('b', lambda a: a + 1)
>>> comp.add_node('c', lambda a: 2 * a)
>>> comp.add_node('d', lambda b, c: b + c)
>>> comp.compute_all()

>>> comp

Gn0an1bn0->n1n2cn0->n2n3dn1->n3n2->n3

This can also be achieved using the .draw() method, which allows some customization of drawing:

>>> comp.draw()

Gn0an1bn0->n1n2cn0->n2n3dn1->n3n2->n3

And the .view() method will produce a graphical representation as a pdf,and display it in the default external pdf viewer - this can be useful to keep a view of the computation graph while making other changes in a notebook.

State information

The following example has been contrived to show every state currently supported by Loman:

>>> comp = Computation()
>>> comp.add_node('a', value=1)
>>> comp.add_node('e', lambda a: 1)
>>> comp.add_node('b', lambda a, doesnotexist: 1)
>>> comp.add_node('c', lambda a: a / 0.)
>>> comp.add_node('d', lambda b, c: b + c)
>>> comp.compute('c')

Gn0an1bn0->n1n2cn0->n2n4en0->n4n3dn1->n3n2->n3n5doesnotexistn5->n1

Color State Meaning
Blue UNINITIALIZED Node has been created, but does not contain a value
Green UPTODATE Node has been set/calculated and is up-to-date
Yellow STALE Upstream nodes have been recalculated, so value is stale
Green-Yellow COMPUTABLE Value is stale/does not exist, but all immediate parent nodes are up-to-date, so node can be recalculated immediately
Red ERROR An error was encountered while calculating this node. Value will contain the Exception object and a Traceback object.
Orange PLACEHOLDER This node was referenced by another node, but has not yet been defined, so a placeholder has been created, pending the definition of this node.

Showing timing information

Loman can also draw graph nodes to show timing information. In the following example, we deliberately create some nodes that are slow to calculate. We can the use Computation.draw(colors='timing') to show which nodes are slow to compute (green is fastest, red is slowest). This can be useful for diagnosing bottlenecks in complex computations.

from loman import *
import time

@ComputationFactory
class ExampleComputation:
    a = input_node()

    @calc_node
    def b(self, a):
        time.sleep(1)
        return a + 1

    @calc_node
    def c(self, a):
        time.sleep(2)
        return 2 * a

    @calc_node
    def d(self, b, c):
        time.sleep(3)
        return b + c
>>> comp = ExampleComputation()
>>> comp.insert('a', 3)
>>> comp.compute_all()
>>> comp.draw(colors='timing')

Gn0an1bn0->n1n2cn0->n2n3dn1->n3n2->n3

Styling

You can control how nodes are rendered using the style keyword of the add_node method or calc_node decorator. Two styles are currently supported: 'small' and 'dot'. This can be useful to de-emphasize nodes in complex computations:

>>> comp = Computation()
>>> comp.add_node('a', value=1, style='small')
>>> comp.add_node('b', lambda a: a + 1, style='dot')
>>> comp.add_node('c', lambda a: 2 * a, style='dot')
>>> comp.add_node('d', lambda b, c: b + c)
>>> comp.compute_all()
>>> comp

Gn0an1n0->n1n2n0->n2n3dn1->n3n2->n3

Showing type information

You can ask loman to graphically show type information for nodes by calling draw with the keyword argument shape='type'. This example shows how different types are shown:

>>> import numpy as np, pandas as pd
>>> comp = Computation()
>>> comp.add_node('scalar', value=1)
>>> comp.add_node('array', lambda scalar: np.array([scalar]))
>>> comp.add_node('dataframe', lambda scalar: pd.DataFrame([[1]], columns=['A']))
>>> comp.add_node('list', lambda scalar: [scalar])
>>> comp.add_node('tuple', lambda scalar: (scalar,))
>>> comp.add_node('dict', lambda scalar: {'a': scalar})
>>> comp.add_node('computation', lambda scalar: Computation())
>>> comp.add_node('other', lambda scalar: object())
>>> comp.compute_all()
>>> comp.draw(shapes='type')

Gn0scalarn1arrayn0->n1n2dataframen0->n2n3listn0->n3n4tuplen0->n4n5dictn0->n5n6computationn0->n6n7othern0->n7