docs: def build_skeleton(nodes, independencies):
          """Estimates a graph skeleton (UndirectedGraph) from a set of independencies
          using (the first part of) the PC algorithm. The independencies can either be
          provided as an instance of the `Independencies`-class or by passing a
          decision function that decides any conditional independency assertion.
          Returns a tuple `(skeleton, separating_sets)`.
  
          If an Independencies-instance is passed, the contained IndependenceAssertions
          have to admit a faithful BN representation. This is the case if
          they are obtained as a set of d-seperations of some Bayesian network or
          if the independence assertions are closed under the semi-graphoid axioms.
          Otherwise the procedure may fail to identify the correct structure.
  
          Parameters
          ----------
          nodes: list, array-like
              A list of node/variable names of the network skeleton.
  
          independencies: Independencies-instance or function.
              The source of independency information from which to build the skeleton.
              The provided Independencies should admit a faithful representation.
              Can either be provided as an Independencies()-instance or by passing a
              function `f(X, Y, Zs)` that returns `True` when X _|_ Y | Zs,
              otherwise `False`. (X, Y being individual nodes and Zs a list of nodes).
  
          Returns
          -------
          skeleton: UndirectedGraph
              An estimate for the undirected graph skeleton of the BN underlying the data.
  
          separating_sets: dict
              A dict containing for each pair of not directly connected nodes a
              separating set ("witnessing set") of variables that makes then
              conditionally independent. (needed for edge orientation procedures)
  
          Reference
          ---------
          [1] Neapolitan, Learning Bayesian Networks, Section 10.1.2, Algorithm 10.2 (page 550)
              http://www.cs.technion.ac.il/~dang/books/Learning%20Bayesian%20Networks(Neapolitan,%20Richard).pdf
          [2] Koller & Friedman, Probabilistic Graphical Models - Principles and Techniques, 2009
              Section 3.4.2.1 (page 85), Algorithm 3.3
  
          Examples
          --------
          >>> from pgmpy.estimators import ConstraintBasedEstimator
          >>> from pgmpy.models import DAG
          >>> from pgmpy.independencies import Independencies
  
          >>> # build skeleton from list of independencies:
          ... ind = Independencies(['B', 'C'], ['A', ['B', 'C'], 'D'])
          >>> # we need to compute closure, otherwise this set of independencies doesn't
          ... # admit a faithful representation:
          ... ind = ind.closure()
          >>> skel, sep_sets = ConstraintBasedEstimator.build_skeleton("ABCD", ind)
          >>> print(skel.edges())
          [('A', 'D'), ('B', 'D'), ('C', 'D')]
  
          >>> # build skeleton from d-seperations of DAG:
          ... model = DAG([('A', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'E')])
          >>> skel, sep_sets = ConstraintBasedEstimator.build_skeleton(model.nodes(), model.get_independencies())
          >>> print(skel.edges())
          [('A', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'E')]
          """
  
          nodes = list(nodes)
  
          if isinstance(independencies, Independencies):
              def is_independent(X, Y, Zs):
                  return IndependenceAssertion(X, Y, Zs) in independencies
          elif callable(independencies):
              is_independent = independencies
          else:
              raise ValueError("'independencies' must be either Independencies-instance " +
                               "or a ternary function that decides independencies.")
  
          graph = UndirectedGraph(combinations(nodes, 2))
          lim_neighbors = 0
          separating_sets = dict()
          while not all([len(list(graph.neighbors(node))) < lim_neighbors for node in nodes]):
              for node in nodes:
                  for neighbor in list(graph.neighbors(node)):
                      # search if there is a set of neighbors (of size lim_neighbors)
                      # that makes X and Y independent:
                      for separating_set in combinations(set(graph.neighbors(node)) - set([neighbor]), lim_neighbors):
                          if is_independent(node, neighbor, separating_set):
                              separating_sets[frozenset((node, neighbor))] = separating_set
                              graph.remove_edge(node, neighbor)
                              break
              lim_neighbors += 1
  
          return graph, separating_sets