docs: def is_entailed_by(self, other):
          """
          Returns True iff Other 
          (a) has the same domain,
          (b) the same or more specific 'upper' bound; and 
          (c) the same or more general 'lower' bound. 
          
          Approach: Looks for ways to rule out entailment.  If none are found, 
          returns True
          """
  
          
          if not self.is_domain_equal(other):
              return False
          
          """        
          First we compare the similarity between upper bounds, 
           (1) if Self's upper bound is empty, Other's upper bound will always
              be as or more specific.
           (2) if Self's upper bound is a subset of Other's, Other will always
              be as or more specific.
              
           To prove the other has a more specific upper bound, we eliminate
              cases where it does not.  The first condition ensures the above
              two cases are not true.   Then, for each element in Other's upper
              bound (that is not in Self's),
              
           (3) if, for all elements in other's upper bound, we cannot find a member
             in the complete upper bound that is more general (has path from self[i]
             to other[j]), we return False, indicating Other does not entail
             Self.
          """
          domain = self.get_domain()
          self_full_upper = self.compute_upper_bound()
          if not (len(self.upper) == 0 \
               or other.upper.issuperset(self.upper)):
              for self_up in self_full_upper - other.upper:
                  found = False
                  # find a more specific member for each member
                  # of self.upper that's not in other.upper
                  for other_up in other.upper - self_full_upper:
                      if has_path(domain, self_up, other_up):
                          found = True
                          break
                  if found:
                      break
                  else:
                      return False  # none was found     
          """        
          Other must also share the same or more general 'lower' bound. 
  
           (3) if, for all elements in other's lower bound, we cannot find a member
             in the complete upper bound that is more general (has path from self[i]
             to other[j]), we stop.
  
          """
          if not (len(self.lower) == 0 \
              or self.lower.issubset(other.lower)):
              for self_lo in self.lower - other.lower:
                  found = False
                  for other_lo in other.lower - self.lower:
                      if has_path(domain, self_lo, other_lo):
                          found =True
                          break
                  if found:
                      break
                  else:
                      return False  # none was found
                      
          # if we got this far, we should have an entailment!
          return True