class_docstr: Class for representing factor graph.
  
  DiscreteFactor graph is a bipartite graph representing factorization of a function.
  They allow efficient computation of marginal distributions through sum-product
  algorithm.
  
  A factor graph contains two types of nodes. One type corresponds to random
  variables whereas the second type corresponds to factors over these variables.
  The graph only contains edges between variables and factor nodes. Each factor
  node is associated with one factor whose scope is the set of variables that
  are its neighbors.
  
  Parameters
  ----------
  data: input graph
      Data to initialize graph. If data=None (default) an empty graph is
      created. The data is an edge list.
  
  Examples
  --------
  Create an empty FactorGraph with no nodes and no edges
  
  >>> from pgmpy.models import FactorGraph
  >>> G = FactorGraph()
  
  G can be grown by adding variable nodes as well as factor nodes
  
  **Nodes:**
  
  Add a node at a time or a list of nodes.
  
  >>> G.add_node('a')
  >>> G.add_nodes_from(['a', 'b'])
  >>> phi1 = DiscreteFactor(['a', 'b'], [2, 2], np.random.rand(4))
  >>> G.add_factors(phi1)
  >>> G.add_nodes_from([phi1])
  
  **Edges:**
  
  G can also be grown by adding edges.
  
  >>> G.add_edge('a', phi1)
  
  or a list of edges
  
  >>> G.add_edges_from([('a', phi1), ('b', phi1)])
code: def __init__(self, ebunch=None):
      super(FactorGraph, self).__init__()
      if ebunch:
          self.add_edges_from(ebunch)
      self.factors = []
  
imports: import itertools
  from collections import defaultdict
  import numpy
  from networkx.algorithms import bipartite
  from pgmpy.models.MarkovNetwork import MarkovNetwork
  from pgmpy.base import UndirectedGraph
  from pgmpy.factors.discrete import DiscreteFactor
  from pgmpy.factors import factor_product
label_desc: Use image node_id 1 to create a new FactorGraph object from inherited base classes: UndirectedGraph with example: obj = FactorGraph(ebunch)