code: def build_join_tree(dag, clique_priority_func=priority_func):
      # First we will create an undirected copy
      # of the dag
      gu = make_undirected_copy(dag)
  
      # Now we create a copy of the undirected graph
      # and connect all pairs of parents that are
      # not already parents called the 'moralized' graph.
      gm = make_moralized_copy(gu, dag)
  
      # Now we triangulate the moralized graph...
      cliques, elimination_ordering = triangulate(
          gm, clique_priority_func
      )
  
      # Now we initialize the forest and sepsets
      # Its unclear from Darwiche Huang whether we
      # track a sepset for each tree or whether its
      # a global list????
      # We will implement the Join Tree as an undirected
      # graph for now...
  
      # First initialize a set of graphs where
      # each graph initially consists of just one
      # node for the clique. As these graphs get
      # populated with sepsets connecting them
      # they should collapse into a single tree.
      forest = set()
      for clique in cliques:
          jt_node = JoinTreeCliqueNode(clique)
          # Track a reference from the clique
          # itself to the node, this will be
          # handy later... (alternately we
          # could just collapse clique and clique
          # node into one class...
          clique.node = jt_node
          tree = JoinTree([jt_node])
          forest.add(tree)
  
      # Initialize the SepSets
      S = set()  # track the sepsets
      for X, Y in combinations(cliques, 2):
          if X.nodes.intersection(Y.nodes):
              S.add(SepSet(X, Y))
      sepsets_inserted = 0
      while sepsets_inserted < (len(cliques) - 1):
          # Adding in name to make this sort deterministic
          deco = [(s, -1 * s.mass, s.cost, s.__repr__()) for s in S]
          deco.sort(key=lambda x: x[1:])
          candidate_sepset = deco[0][0]
          for candidate_sepset, _, _, _ in deco:
              if candidate_sepset.insertable(forest):
                  # Insert into forest and remove the sepset
                  candidate_sepset.insert(forest)
                  S.remove(candidate_sepset)
                  sepsets_inserted += 1
                  break
  
      assert len(forest) == 1
      jt = list(forest)[0]
      return jt
  
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 11 for calling a global function with example usage: build_join_tree(dag, clique_priority_func) and returns: jt