295    def __init__(self, iterable=None, *, allow_overlaps=False, allow_startone=False):
 
  298            >>> a = IoVSet([IntervalOfValidity(3,6,3,-1), (0,0,3,5)])
 
  303            iterable: if not None it should be an iterable of IntervalOfValidity
 
  304                objects or anything that can be converted to an IntervalOfValidity.
 
  305            allow_overlaps (bool): If False adding which overlaps with any
 
  306                existing iov in the set will raise a ValueError.
 
  307            allow_startone (bool): If True also join iovs if one covers the
 
  308                whole experiment and the next one starts at run 1 in the next
 
  309                experiment. If False they will only be joined if the next one
 
  314        ## Whether or not we raise an error on overlaps
 
  315        self.__allow_overlaps = allow_overlaps
 
  316        ## Whether or not run 1 will be also considered the first run when
 
  317        # combining iovs between experiments
 
  318        self.__allow_startone = allow_startone
 
  319        if iterable is not None:
 
  320            for element in iterable:
 
 
  323    def add(self, iov, allow_overlaps=None):
 
  325        Add a new iov to the set.
 
  327        The new iov be combined with existing iovs if possible. After the
 
  328        operation the set will contain the minimal amount of separate iovs
 
  329        possible to represent all added iovs
 
  332            >>> a.add((0, 0, 0, 2))
 
  333            >>> a.add((0, 3, 0, 5))
 
  334            >>> a.add((0, 8, 0, 9))
 
  336            {(0, 0, 0, 5), (0, 8, 0, 9)}
 
  337            >>> a.add(IoVSet([(10, 0, 10, 1), (10, 2, 10, -1)]))
 
  339            {(0, 0, 0, 5), (0, 8, 0, 9), (10, 0, 10, inf)}
 
  341        Be aware, by default it's not possible to add overlapping iovs to the set.
 
  342        This can be changed either on construction or per `add` call using
 
  345            >>> a.add((0, 2, 0, 3))
 
  346            Traceback (most recent call last):
 
  348            ValueError: Overlap between (0, 0, 0, 5) and (0, 2, 0, 3)
 
  349            >>> a.add((0, 2, 0, 3), allow_overlaps=True)
 
  351            {(0, 0, 0, 5), (0, 8, 0, 9), (10, 0, 10, inf)}
 
  354            iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
 
  355                set of IoVs to add to this set
 
  356            allow_overlaps (bool): Can be used to override global overlap setting
 
  357                of this set to allow/restrict overlaps for a single insertion
 
  361            This method modifies the set in place
 
  363        # check whether we override overlap settings
 
  364        if allow_overlaps is None:
 
  365            allow_overlaps = self.__allow_overlaps
 
  366        # we can add a set to a set :D
 
  367        if isinstance(iov, IoVSet):
 
  369                self.add(element, allow_overlaps)
 
  371        # make sure it's actually an IoV, this will raise an error on failure
 
  372        if not isinstance(iov, IntervalOfValidity):
 
  373            iov = IntervalOfValidity(iov)
 
  374        # and now check for all existing iovs ... (but use a copy since we modify the set)
 
  375        for existing in list(self.__iovs):
 
  376            # if there's an overlap to the new iov
 
  377            if (not allow_overlaps) and (existing & iov):
 
  378                raise ValueError(f"Overlap between {existing} and {iov}")
 
  379            # and if they can be combined to a bigger iov
 
  380            combined = existing.union(iov, self.__allow_startone)
 
  381            # if we now have a combined iov, remove the one that we were able to
 
  382            # combine it with from the existing iovs because we now check
 
  383            # against the combined one. Since the only way to add a new iov is
 
  384            # this loop we know all previous existing iovs we checked before
 
  385            # didn't have a union with this new iov or any other existing iovs
 
  386            # so if the just check the remaining iovs against the new combined
 
  387            # one we can cascade combine all iovs in one go.
 
  388            if combined is not None:
 
  389                self.__iovs.remove(existing)
 
  391        # done, we now have a new iov which combines all existing iovs it had an
 
  392        # overlap with and we removed the existing iovs so nothing else to do
 
  393        # but add the iov back in the list
 
 
  396    def remove(self, iov):
 
  397        """Remove an iov or a set of iovs from this set
 
  399        After this operation the set will not be valid for the given iov or set
 
  403            >>> a.add((0,0,10,-1))
 
  404            >>> a.remove((1,0,1,-1))
 
  405            >>> a.remove((5,0,8,5))
 
  407            {(0, 0, 0, inf), (2, 0, 4, inf), (8, 6, 10, inf)}
 
  408            >>> a.remove(IoVSet([(3,0,3,10), (3,11,3,-1)]))
 
  410            {(0, 0, 0, inf), (2, 0, 2, inf), (4, 0, 4, inf), (8, 6, 10, inf)}
 
  413            iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
 
  414                set of IoVs to remove from this set
 
  417            This method modifies the set in place
 
  419        # we can remove a set from a set :D
 
  420        if isinstance(iov, IoVSet):
 
  424        # make sure it's actually an IoV, this will raise an error on failure
 
  425        if not isinstance(iov, IntervalOfValidity):
 
  426            iov = IntervalOfValidity(iov)
 
  427        # and subtract the iov from all existing iovs
 
  428        for existing in list(self.__iovs):
 
  429            delta = existing - iov
 
  430            if delta != existing:
 
  431                self.__iovs.remove(existing)
 
  432                if isinstance(delta, tuple):
 
  433                    # got two new iovs, apparently we split the old one
 
  436                elif delta is not None:
 
  437                    self.__iovs.add(delta)
 
 
  439    def intersect(self, iov):
 
  440        """Intersect this set with another set and return a new set
 
  441        which is valid exactly where both sets have been valid before
 
  444            >>> a.add((0,0,10,-1))
 
  445            >>> a.intersect((5,0,20,-1))
 
  447            >>> a.intersect(IoVSet([(0,0,3,-1), (9,0,20,-1)]))
 
  448            {(0, 0, 3, inf), (9, 0, 10, inf)}
 
  451            iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
 
  452                set of IoVs to intersect with this set
 
  454        if not isinstance(iov, (IoVSet, IntervalOfValidity)):
 
  455            iov = IntervalOfValidity(iov)
 
  456        if isinstance(iov, IntervalOfValidity):
 
  459        # ok for all combinations a,b from set1 and set2 check the intersection
 
  460        # and if not empty add to the result
 
  462        for a, b in product(self.iovs, iov.iovs):
 
 
  468    def contains(self, iov):
 
  470        Check if an iov is fully covered by the set
 
  472            >>> a = IoVSet([(0,0,2,-1), (5,0,5,-1)])
 
  473            >>> a.contains((0,0,1,-1))
 
  475            >>> a.contains(IntervalOfValidity(0,0,3,2))
 
  477            >>> a.contains(IoVSet([(0,1,1,23), (5,0,5,23)]))
 
  479            >>> a.contains(IoVSet([(0,1,1,23), (5,0,6,23)]))
 
  481            >>> a.contains((3,0,4,-1))
 
  485            iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
 
  486                set of IoVs to be checked
 
  489            True if the full iov or all the iovs in the given set are fully
 
  492        # check if the whole set is in this set: all iovs need to be in here
 
  493        if isinstance(iov, IoVSet):
 
  494            return all(e in self for e in iov)
 
  495        # make sure it's actually an IoV, this will raise an error on failure
 
  496        if not isinstance(iov, IntervalOfValidity):
 
  497            iov = IntervalOfValidity(iov)
 
  498        # and then check all iovs in the set if they cover it
 
  499        for existing in self.__iovs:
 
  500            if iov - existing is None:
 
 
  504    def overlaps(self, iov):
 
  505        """Check if the given iov overlaps with this set.
 
  507        In contrast to `contains` this doesn't require the given iov to be fully
 
  508        covered. It's enough if the any run covered by the iov is also covered
 
  511            >>> a = IoVSet([(0,0,2,-1), (5,0,5,-1)])
 
  512            >>> a.overlaps((0,0,1,-1))
 
  514            >>> a.overlaps(IntervalOfValidity(0,0,3,2))
 
  516            >>> a.overlaps(IoVSet([(0,1,1,23), (5,0,5,23)]))
 
  518            >>> a.overlaps(IoVSet([(0,1,1,23), (5,0,6,23)]))
 
  520            >>> a.overlaps((3,0,4,-1))
 
  524            iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
 
  525                set of IoVs to be checked
 
  528            True if the iov or any of the iovs in the given set overlap with any
 
  531        if not isinstance(iov, (IoVSet, IntervalOfValidity)):
 
  532            iov = IntervalOfValidity(iov)
 
  533        if isinstance(iov, IntervalOfValidity):
 
  536        for a, b in product(self.iovs, iov.iovs):
 
 
  583        """Return the gaps in the set. Any area not covered between the first
 
  584        point of validity and the last
 
  586        >>> a = IoVSet([(0,0,2,-1)])
 
  589        >>> b = IoVSet([(0,0,2,-1), (5,0,5,-1)])
 
  592        >>> c = IoVSet([(0,0,2,-1), (5,0,5,-1), (10,3,10,6)])
 
  594        {(3, 0, 4, inf), (6, 0, 10, 2)}
 
  596        if len(self.__iovs) < 2:
 
  599        full_range = IoVSet([self.first + self.final])
 
  600        return full_range - self
 
 
  631    def __or__(self, other):
 
  633        Return a new set that is the combination of two sets: The new set will
 
  634        be valid everywhere any of the two sets were valid.
 
  636        No check for overlaps will be performed but the result will inherit the
 
  637        settings for further additions from the first set
 
  639            >>> a = IoVSet([(0,0,1,-1)])
 
  643            {(0, 0, 1, inf), (3, 0, 3, inf)}
 
  646        copy.add(other, allow_overlaps=True)
 
 
  649    def __sub__(self, other):
 
  651        Return a new set which is only valid for where a is valid but not b.
 
  653        See `remove` but this will not modify the set in place
 
  655            >>> a = IoVSet([(0,0,-1,-1)])
 
  657            {(0, 0, 0, inf), (3, 0, inf, inf)}
 
  658            >>> a - (0,0,3,-1) - (10,0,-1,-1)
 
  660            >>> IoVSet([(0,0,1,-1)]) - (2,0,2,-1)