def _get_head_nodes(root_ctx):
    class Vertex:
        """vertex in the digraph of (node, ctx) -> (node, ctx)"""
        def __init__(self, node, ctx):
            """don't call directly, use get"""
            self.node = node
            self.ctx = ctx
            self.__edges_in = set()
            self.__edges_out = set()

        @classmethod
        def add_edge(self, lhs, rhs):
            """add directed edge lhs -> rhs"""
            lhs.__edges_out.add((rhs.node, rhs.ctx))
            rhs.__edges_in.add((lhs.node, lhs.ctx))

        @property
        def edges_out(self):
            return [self.get(n, c) for n, c in self.__edges_out]

        @property
        def edges_in(self):
            return [self.get(n, c) for n, c in self.__edges_in]

        @property
        def in_degree(self):
            return len(self.__edges_in)

        @property
        def out_degree(self):
            return len(self.__edges_out)

        _node_ctx_to_verts = {} 
        @classmethod
        def get(cls, node, ctx):
            v = cls._node_ctx_to_verts.get((node, ctx), None)
            if v is None:
                v = cls(node, ctx)
                cls._node_ctx_to_verts[(node, ctx)] = v
            return v  

        @classmethod
        def all_vertices(cls):
            return cls._node_ctx_to_verts.values()

    #
    # build the set of vertices
    #
    root_ctx = root_ctx.get_parent() or root_ctx
    contexts = [root_ctx] + root_ctx.get_shifted_contexts()
    for ctx in contexts:
        nodes = ctx.all_nodes()
        for node in nodes:
            left = Vertex.get(node, ctx)
            for dep_node, dep_ctx in node.get_dependencies(ctx):
                right = Vertex.get(dep_node, dep_ctx)
                Vertex.add_edge(left, right)

    # sort by in_degree so we can find the vertices with the minimum
    vertices = Vertex.all_vertices()
    vertices.sort(key=lambda x: x.in_degree)

    head_vertices = set()
    while vertices:
        # select the vertex with the minimum in degree
        v = vertices.pop(0)
        head_vertices.add(v)

        # remove its dependencies from the set left to 
        # be considered and the set of previously found
        # best nodes
        edges_out = list(v.edges_out)
        seen = set([v])
        while edges_out:
            e = edges_out.pop()
            if e in seen:
                continue

            edges_out.extend(e.edges_out)
            seen.add(e)

            for s in (vertices, head_vertices):
                try:
                    s.remove(e)
                except (KeyError, ValueError):
                    pass

    return [(x.node, x.ctx) for x in head_vertices]
returns a list of (context, node) tuples from which all other
dataset/ETHPy150Open manahl/mdf/mdf/viewer/frame.py/_get_head_nodes