code: def _get_node_groups(self):
          
          node_dict = {node['data']['id']: {'sources': [], 'targets': []}
                       for node in self._nodes}
          for edge in self._edges:
              # Add edge as a source for its target node
              edge_data = (edge['data']['i'], edge['data']['polarity'],
                           edge['data']['source'])
              node_dict[edge['data']['target']]['sources'].append(edge_data)
              # Add edge as target for its source node
              edge_data = (edge['data']['i'], edge['data']['polarity'],
                           edge['data']['target'])
              node_dict[edge['data']['source']]['targets'].append(edge_data)
          # Make a dictionary of nodes based on source/target as a key
          node_key_dict = collections.defaultdict(lambda: [])
          for node_id, node_d in node_dict.items():
              key = self._get_node_key(node_d)
              node_key_dict[key].append(node_id)
          # Constrain the groups to ones that have more than 1 member
          node_groups = [g for g in node_key_dict.values() if (len(g) > 1)]
          return node_groups
docs: Return a list of node id lists that are topologically identical.
  
          First construct a node_dict which is keyed to the node id and
          has a value which is a dict with keys 'sources' and 'targets'.
          The 'sources' and 'targets' each contain a list of tuples
          (i, polarity, source) edge of the node. node_dict is then processed
          by _get_node_key() which returns a tuple of (s,t) where s,t are
          sorted tuples of the ids for the source and target nodes. (s,t) is
          then used as a key in node_key_dict where the values are the node
          ids. node_groups is restricted to groups greater than 1 node.