docs: def get_hyperpath_from_predecessors(H, Pv, source_node, destination_node,
                                      node_weights=None, attr_name="weight"):
      """Gives the hyperpath (DirectedHypergraph) representing the shortest
      B-hyperpath from the source to the destination, given a predecessor
      function and source and destination nodes.
  
      :note: The IDs of the hyperedges in the subhypergraph returned may be
          different than those in the original hypergraph (even though the
          tail and head sets are identical).
  
      :param H: the hypergraph which the path algorithm was executed on.
      :param Pv: dictionary mapping each node to the ID of the hyperedge that
              preceeded it in the path.
      :param source_node: the source node of the path.
      :param destination_node: the destination node of the path.
      :returns: DirectedHypergraph -- shortest B-hyperpath from source_node to
              destination_node.
      :raises: TypeError -- Algorithm only applicable to directed hypergraphs
      :raises: KeyError -- Node key in predecessor is not in H
      :raises: KeyError -- Hyperedge key in predecessor is not in H
      :raises: ValueError -- Multiple nodes without predecessor
      :raises: ValueError -- Hypertree does not have source node
  
      """
      if not isinstance(H, DirectedHypergraph):
          raise TypeError("Algorithm only applicable to directed hypergraphs")
  
      # Check that Pv is a valid predecessor function:
      # - keys must be nodes in H mapping to hyperedges in H
      # - exactly one node must map to None (i.e., only one node
      #   without predecessor)
      nodes_without_predecessor = 0
      for node, hyperedge_id in Pv.items():
          if not H.has_node(node):
              raise KeyError(
                  "Node key %s in predecessor is not in H" % node)
  
          if hyperedge_id is None:
              nodes_without_predecessor += 1
          elif not H.has_hyperedge_id(hyperedge_id):
              raise KeyError(
                  "Hyperedge key %s in predecessor is not in H" % hyperedge_id)
  
      if nodes_without_predecessor > 1:
          raise ValueError(
              "Multiple nodes without predecessor. %s received" % Pv)
      elif nodes_without_predecessor == 0:
          raise ValueError(
              "Hypertree does not have source node. %s received" % Pv)
  
      path = DirectedHypergraph()
  
      # keep track of which nodes are or have been processed
      processedOrInQueue = {n: False for n in Pv}
      nodesToProcess = [destination_node]
      processedOrInQueue[destination_node] = True
      while nodesToProcess:
          node = nodesToProcess.pop(0)
          hyperedge_id = Pv[node]
          if hyperedge_id:
              for n in H.get_hyperedge_tail(hyperedge_id):
                  if not processedOrInQueue[n]:
                      nodesToProcess.append(n)
                      processedOrInQueue[n] = True
              path.add_hyperedge(H.get_hyperedge_tail(hyperedge_id),
                                 H.get_hyperedge_head(hyperedge_id),
                                 weight=H.get_hyperedge_weight(
                                 hyperedge_id))
          elif not path.has_node(node):
              path.add_node(node)
  
      return path