code: def junction_tree(G):
      r"""Returns a junction tree of a given graph.
  
      A junction tree (or clique tree) is constructed from a (un)directed graph G.
      The tree is constructed based on a moralized and triangulated version of G.
      The tree's nodes consist of maximal cliques and sepsets of the revised graph.
      The sepset of two cliques is the intersection of the nodes of these cliques,
      e.g. the sepset of (A,B,C) and (A,C,E,F) is (A,C). These nodes are often called
      "variables" in this literature. The tree is bipartite with each sepset
      connected to its two cliques.
  
      Junction Trees are not unique as the order of clique consideration determines
      which sepsets are included.
  
      The junction tree algorithm consists of five steps [1]_:
  
      1. Moralize the graph
      2. Triangulate the graph
      3. Find maximal cliques
      4. Build the tree from cliques, connecting cliques with shared
         nodes, set edge-weight to number of shared variables
      5. Find maximum spanning tree
  
  
      Parameters
      ----------
      G : networkx.Graph
          Directed or undirected graph.
  
      Returns
      -------
      junction_tree : networkx.Graph
          The corresponding junction tree of `G`.
  
      Raises
      ------
      NetworkXNotImplemented
          Raised if `G` is an instance of `MultiGraph` or `MultiDiGraph`.
  
      References
      ----------
      .. [1] Junction tree algorithm:
         https://en.wikipedia.org/wiki/Junction_tree_algorithm
  
      .. [2] Finn V. Jensen and Frank Jensen. 1994. Optimal
         junction trees. In Proceedings of the Tenth international
         conference on Uncertainty in artificial intelligence (UAI’94).
         Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 360–366.
      """
  
      clique_graph = nx.Graph()
  
      if G.is_directed():
          G = moral.moral_graph(G)
      chordal_graph, _ = complete_to_chordal_graph(G)
  
      cliques = [
          tuple(sorted(i)) for i in chordal_graph_cliques(chordal_graph)
      ]
      clique_graph.add_nodes_from(cliques, type="clique")
  
      for edge in combinations(cliques, 2):
          set_edge_0 = set(edge[0])
          set_edge_1 = set(edge[1])
          if not set_edge_0.isdisjoint(set_edge_1):
              sepset = tuple(
                  sorted(set_edge_0.intersection(set_edge_1))
              )
              clique_graph.add_edge(
                  edge[0], edge[1], weight=len(sepset), sepset=sepset
              )
  
      junction_tree = nx.maximum_spanning_tree(clique_graph)
  
      for edge in list(junction_tree.edges(data=True)):
          junction_tree.add_node(edge[2]["sepset"], type="sepset")
          junction_tree.add_edge(edge[0], edge[2]["sepset"])
          junction_tree.add_edge(edge[1], edge[2]["sepset"])
          junction_tree.remove_edge(edge[0], edge[1])
  
      return junction_tree
  
imports: from itertools import combinations
  import networkx
  from networkx.algorithms import chordal_graph_cliques, complete_to_chordal_graph, moral
  from networkx.utils import not_implemented_for
label_desc: Use image node_id 1 for calling a global function with example usage: junction_tree(G) and returns: junction_tree