docs: def replace(self, left=None, lower=None, upper=None, right=None, ignore_inf=True):
          """
          Create a new interval based on the current one and the provided values.
  
          Callable can be passed instead of values. In that case, it is called with the current
          corresponding value except if ignore_inf if set (default) and the corresponding
          bound is an infinity.
  
          :param left: (a function of) left boundary.
          :param lower: (a function of) value of the lower bound.
          :param upper: (a function of) value of the upper bound.
          :param right: (a function of) right boundary.
          :param ignore_inf: ignore infinities if functions are provided (default is True).
          :return: an Interval instance
          """
          if callable(left):
              left = left(self._left)
          else:
              left = self._left if left is None else left
  
          if callable(lower):
              lower = self._lower if ignore_inf and self._lower in [-inf, inf] else lower(self._lower)
          else:
              lower = self._lower if lower is None else lower
  
          if callable(upper):
              upper = self._upper if ignore_inf and self._upper in [-inf, inf] else upper(self._upper)
          else:
              upper = self._upper if upper is None else upper
  
          if callable(right):
              right = right(self._right)
          else:
              right = self._right if right is None else right
  
          return AtomicInterval(left, lower, upper, right)