code: def compute_upper_bound(self):
          
          nub = set()
          for root in self.roots - self.upper:
              found = False
              for up in self.upper - self.roots:
                  domain = self.get_domain()
                  if has_path(domain, root, up):
                      found = True
                      break
              if not found:
                  nub.add(root)
          return nub | (self.upper - self.roots)
docs: We have to compute the new upper boundary (nub) starting from the
          root nodes.  If a path exists between a root node and another entry in
          `self.upper` we can ignore the root node because it has been
          specialized by one of its successors.