code: def assign_clusters(self, bbn):
      assignments_by_family = {}
      assignments_by_clique = defaultdict(list)
      assigned = set()
      for node in bbn.nodes:
          args = get_args(node.func)
          if len(args) == 1:
              # If the func has only 1 arg
              # it means that it does not
              # specify a conditional probability
              # This is where H&D is a bit vague
              # but it seems to imply that we
              # do not assign it to any
              # clique.
              # Revising this for now as I dont
              # think its correct, I think
              # all CPTs need to be assigned
              # once and once only. The example
              # in H&D just happens to be a clique
              # that f_a could have been assigned
              # to but wasnt presumably because
              # it got assigned somewhere else.
              pass
              # continue
          # Now we need to find a cluster that
          # is a superset of the Family(v)
          # Family(v) is defined by D&H to
          # be the union of v and parents(v)
          family = set(args)
          # At this point we need to know which *variable*
          # a BBN node represents. Up to now we have
          # not *explicitely* specified this, however
          # we have been following some conventions
          # so we could just use this convention for
          # now. Need to come back to this to
          # perhaps establish the variable at
          # build bbn time...
  
          containing_cliques = [
              clique_node
              for clique_node in self.clique_nodes
              if (set(clique_node.variable_names).issuperset(family))
          ]
          assert len(containing_cliques) >= 1
          for clique in containing_cliques:
              if node in assigned:
                  # Make sure we assign all original
                  # PMFs only once each
                  break
              assignments_by_clique[clique].append(node)
              assigned.add(node)
          assignments_by_family[tuple(family)] = containing_cliques
      return assignments_by_clique
  
imports: import copy
  import heapq
  import logging
  from collections import defaultdict
  from io import StringIO
  from itertools import combinations, product
  from random import choice, random
  from .exceptions import VariableNotInGraphError, VariableValueNotInDomainError
  from .graph import Node, UndirectedGraph, UndirectedNode, connect
  from .utils import get_args, get_original_factors
label_desc: Use image node_id 6 for calling the JoinTree obj's underlying member method code with example usage: obj.assign_clusters(bbn) and returns: assignments_by_clique