docs: def _is_converged(self, operation):
          """
          Checks whether the calibration has converged or not. At convergence
          the sepset belief would be precisely the sepset marginal.
  
          Parameters
          ----------
          operation: str ('marginalize' | 'maximize')
              The operation to do for passing messages between nodes.
              if operation == marginalize, it checks whether the junction tree is calibrated or not
              else if operation == maximize, it checks whether the juction tree is max calibrated or not
  
          Formally, at convergence or at calibration this condition would be satisified for
  
          .. math:: \sum_{C_i - S_{i, j}} \beta_i = \sum_{C_j - S_{i, j}} \beta_j = \mu_{i, j}
  
          and at max calibration this condition would be satisfied
  
          .. math:: \max_{C_i - S_{i, j}} \beta_i = \max_{C_j - S_{i, j}} \beta_j = \mu_{i, j}
          """
          # If no clique belief, then the clique tree is not calibrated
          if not self.clique_beliefs:
              return False
  
          for edge in self.junction_tree.edges():
              sepset = frozenset(edge[0]).intersection(frozenset(edge[1]))
              sepset_key = frozenset(edge)
              if (edge[0] not in self.clique_beliefs or edge[1] not in self.clique_beliefs or
                      sepset_key not in self.sepset_beliefs):
                  return False
  
              marginal_1 = getattr(self.clique_beliefs[edge[0]], operation)(list(frozenset(edge[0]) - sepset),
                                                                            inplace=False)
              marginal_2 = getattr(self.clique_beliefs[edge[1]], operation)(list(frozenset(edge[1]) - sepset),
                                                                            inplace=False)
              if marginal_1 != marginal_2 or marginal_1 != self.sepset_beliefs[sepset_key]:
                  return False
          return True