299 def __init__(self, iterable=None, *, allow_overlaps=False, allow_startone=False):
302 >>> a = IoVSet([IntervalOfValidity(3,6,3,-1), (0,0,3,5)])
307 iterable: if not None it should be an iterable of IntervalOfValidity
308 objects or anything that can be converted to an IntervalOfValidity.
309 allow_overlaps (bool): If False adding which overlaps with any
310 existing iov in the set will raise a ValueError.
311 allow_startone (bool): If True also join iovs if one covers the
312 whole experiment and the next one starts at run 1 in the next
313 experiment. If False they will only be joined if the next one
318 ## Whether or not we raise an error on overlaps
319 self.__allow_overlaps = allow_overlaps
320 ## Whether or not run 1 will be also considered the first run when
321 # combining iovs between experiments
322 self.__allow_startone = allow_startone
323 if iterable is not None:
324 for element in iterable:
327 def add(self, iov, allow_overlaps=None):
329 Add a new iov to the set.
331 The new iov be combined with existing iovs if possible. After the
332 operation the set will contain the minimal amount of separate iovs
333 possible to represent all added iovs
336 >>> a.add((0, 0, 0, 2))
337 >>> a.add((0, 3, 0, 5))
338 >>> a.add((0, 8, 0, 9))
340 {(0, 0, 0, 5), (0, 8, 0, 9)}
341 >>> a.add(IoVSet([(10, 0, 10, 1), (10, 2, 10, -1)]))
343 {(0, 0, 0, 5), (0, 8, 0, 9), (10, 0, 10, inf)}
345 Be aware, by default it's not possible to add overlapping iovs to the set.
346 This can be changed either on construction or per `add` call using
349 >>> a.add((0, 2, 0, 3))
350 Traceback (most recent call last):
352 ValueError: Overlap between (0, 0, 0, 5) and (0, 2, 0, 3)
353 >>> a.add((0, 2, 0, 3), allow_overlaps=True)
355 {(0, 0, 0, 5), (0, 8, 0, 9), (10, 0, 10, inf)}
358 iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
359 set of IoVs to add to this set
360 allow_overlaps (bool): Can be used to override global overlap setting
361 of this set to allow/restrict overlaps for a single insertion
365 This method modifies the set in place
367 # check whether we override overlap settings
368 if allow_overlaps is None:
369 allow_overlaps = self.__allow_overlaps
370 # we can add a set to a set :D
371 if isinstance(iov, IoVSet):
373 self.add(element, allow_overlaps)
375 # make sure it's actually an IoV, this will raise an error on failure
376 if not isinstance(iov, IntervalOfValidity):
377 iov = IntervalOfValidity(iov)
378 # and now check for all existing iovs ... (but use a copy since we modify the set)
379 for existing in list(self.__iovs):
380 # if there's an overlap to the new iov
381 if (not allow_overlaps) and (existing & iov):
382 raise ValueError(f"Overlap between {existing} and {iov}")
383 # and if they can be combined to a bigger iov
384 combined = existing.union(iov, self.__allow_startone)
385 # if we now have a combined iov, remove the one that we were able to
386 # combine it with from the existing iovs because we now check
387 # against the combined one. Since the only way to add a new iov is
388 # this loop we know all previous existing iovs we checked before
389 # didn't have a union with this new iov or any other existing iovs
390 # so if the just check the remaining iovs against the new combined
391 # one we can cascade combine all iovs in one go.
392 if combined is not None:
393 self.__iovs.remove(existing)
395 # done, we now have a new iov which combines all existing iovs it had an
396 # overlap with and we removed the existing iovs so nothing else to do
397 # but add the iov back in the list
400 def remove(self, iov):
401 """Remove an iov or a set of iovs from this set
403 After this operation the set will not be valid for the given iov or set
407 >>> a.add((0,0,10,-1))
408 >>> a.remove((1,0,1,-1))
409 >>> a.remove((5,0,8,5))
411 {(0, 0, 0, inf), (2, 0, 4, inf), (8, 6, 10, inf)}
412 >>> a.remove(IoVSet([(3,0,3,10), (3,11,3,-1)]))
414 {(0, 0, 0, inf), (2, 0, 2, inf), (4, 0, 4, inf), (8, 6, 10, inf)}
417 iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
418 set of IoVs to remove from this set
421 This method modifies the set in place
423 # we can remove a set from a set :D
424 if isinstance(iov, IoVSet):
428 # make sure it's actually an IoV, this will raise an error on failure
429 if not isinstance(iov, IntervalOfValidity):
430 iov = IntervalOfValidity(iov)
431 # and subtract the iov from all existing iovs
432 for existing in list(self.__iovs):
433 delta = existing - iov
434 if delta != existing:
435 self.__iovs.remove(existing)
436 if isinstance(delta, tuple):
437 # got two new iovs, apparently we split the old one
440 elif delta is not None:
441 self.__iovs.add(delta)
443 def intersect(self, iov):
444 """Intersect this set with another set and return a new set
445 which is valid exactly where both sets have been valid before
448 >>> a.add((0,0,10,-1))
449 >>> a.intersect((5,0,20,-1))
451 >>> a.intersect(IoVSet([(0,0,3,-1), (9,0,20,-1)]))
452 {(0, 0, 3, inf), (9, 0, 10, inf)}
455 iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
456 set of IoVs to intersect with this set
458 if not isinstance(iov, (IoVSet, IntervalOfValidity)):
459 iov = IntervalOfValidity(iov)
460 if isinstance(iov, IntervalOfValidity):
463 # ok for all combinations a,b from set1 and set2 check the intersection
464 # and if not empty add to the result
466 # \cond false positive doxygen warning
467 for a, b in product(self.iovs, iov.iovs):
474 def contains(self, iov):
476 Check if an iov is fully covered by the set
478 >>> a = IoVSet([(0,0,2,-1), (5,0,5,-1)])
479 >>> a.contains((0,0,1,-1))
481 >>> a.contains(IntervalOfValidity(0,0,3,2))
483 >>> a.contains(IoVSet([(0,1,1,23), (5,0,5,23)]))
485 >>> a.contains(IoVSet([(0,1,1,23), (5,0,6,23)]))
487 >>> a.contains((3,0,4,-1))
491 iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
492 set of IoVs to be checked
495 True if the full iov or all the iovs in the given set are fully
498 # check if the whole set is in this set: all iovs need to be in here
499 if isinstance(iov, IoVSet):
500 return all(e in self for e in iov)
501 # make sure it's actually an IoV, this will raise an error on failure
502 if not isinstance(iov, IntervalOfValidity):
503 iov = IntervalOfValidity(iov)
504 # and then check all iovs in the set if they cover it
505 for existing in self.__iovs:
506 if iov - existing is None:
510 def overlaps(self, iov):
511 """Check if the given iov overlaps with this set.
513 In contrast to `contains` this doesn't require the given iov to be fully
514 covered. It's enough if the any run covered by the iov is also covered
517 >>> a = IoVSet([(0,0,2,-1), (5,0,5,-1)])
518 >>> a.overlaps((0,0,1,-1))
520 >>> a.overlaps(IntervalOfValidity(0,0,3,2))
522 >>> a.overlaps(IoVSet([(0,1,1,23), (5,0,5,23)]))
524 >>> a.overlaps(IoVSet([(0,1,1,23), (5,0,6,23)]))
526 >>> a.overlaps((3,0,4,-1))
530 iov (Union[IoVSet, IntervalOfValidity, tuple(int)]): IoV or
531 set of IoVs to be checked
534 True if the iov or any of the iovs in the given set overlap with any
537 if not isinstance(iov, (IoVSet, IntervalOfValidity)):
538 iov = IntervalOfValidity(iov)
539 if isinstance(iov, IntervalOfValidity):
542 # \cond false positive doxygen warning
543 for a, b in product(self.iovs, iov.iovs):
570 >>> a = IoVSet([(3,0,3,10), (10,11,10,23), (0,0,2,-1), (5,0,5,-1)])
582 >>> a = IoVSet([(3,0,3,10), (10,11,10,23), (0,0,2,-1), (5,0,5,-1)])
593 """Return the gaps in the set. Any area not covered between the first
594 point of validity and the last
596 >>> a = IoVSet([(0,0,2,-1)])
599 >>> b = IoVSet([(0,0,2,-1), (5,0,5,-1)])
602 >>> c = IoVSet([(0,0,2,-1), (5,0,5,-1), (10,3,10,6)])
604 {(3, 0, 4, inf), (6, 0, 10, 2)}
606 if len(self.__iovs) < 2:
609 full_range = IoVSet([self.first + self.final])
610 return full_range - self
641 def __or__(self, other):
643 Return a new set that is the combination of two sets: The new set will
644 be valid everywhere any of the two sets were valid.
646 No check for overlaps will be performed but the result will inherit the
647 settings for further additions from the first set
649 >>> a = IoVSet([(0,0,1,-1)])
653 {(0, 0, 1, inf), (3, 0, 3, inf)}
656 copy.add(other, allow_overlaps=True)
659 def __sub__(self, other):
661 Return a new set which is only valid for where a is valid but not b.
663 See `remove` but this will not modify the set in place
665 >>> a = IoVSet([(0,0,-1,-1)])
667 {(0, 0, 0, inf), (3, 0, inf, inf)}
668 >>> a - (0,0,3,-1) - (10,0,-1,-1)
670 >>> IoVSet([(0,0,1,-1)]) - (2,0,2,-1)