User guide

Terminology

By domain we mean a set of values forming a continuum, spanning all the way from “minus infinity” to “plus infinity”. Obvious examples are the real numbers, the timeline, but any other totally-ordered, unbound, continuous set would do.

Interval Sets are a collection of zero, one or more Intervals. Each Interval is the set made of all points in the domain comprised between its two ends (each an IntervalPeg). The values themselves at the pegs can be included or not in the interval, as specified by the mathematical notation:

[a, b] = { x / a <= x <= b }
(a, b) = { x / a <  x <  b }

An Interval can span all the way to the infinities.

An IntervalSet is the union of an arbitrary (finite) number of Intervals. In the internal representation of Interval Sets in clothesline, interval sets are always reduced to a canonical form, which enables, among other advantages, a meaningful implementation of the equality operator.

Note

In other words, an Interval is an uninterrupted segment within the domain, whil an Interval Set is any set of points, possibly with “holes”.

The clothesline library lets you create Interval Set in several ways, operate on them, combine them, store/retrieve them in serialized form. You should be able to do everything you need without the need to reach the lower-level objects such as Interval and IntervalPeg.

Interval Sets are meant for use as immutable objects.

clothesline offers ready-made classes to handle Interval Sets over two domains: real numbers (clothesline.RealIntervalSet) and datetime.datetime objects (clothesline.DatetimeIntervalSet). In the follow we mostly stick to the former, but analogous usage patterns hold (with the input values adapted appropriately).

Warning

It is unwise, and not supported, to mix Interval Sets built on different domains. That said, regardless of the domain, the symbols representing the infinities are the same.

Set creation

There are various ways to create Interval Sets.

Builder

One can get a builder from the class and use it to generate single-interval Interval Sets, with a syntax reminiscent of the above mathematical notation:

import clothesline
bld = clothesline.RealIntervalSet.builder()

bset1 = bld[0][1]     # [0, 1]
bset2 = bld(7)(9)     # (7, 9)
bset3 = bld[0](10)    # [0, 10)
bset4 = bld(...)(6)   # (-inf, 6)
bset5 = bld[-8][...]  # [-8, +inf)

The ellipsis, depending on the position, would get translated to the correct infinity. If the end point is lesser than the start point, a clothesline.exceptions.InvalidValueError is raised.

Usage of the builder, as for the utils case below, results in single-interval Interval Sets: to build more generic sets, one can make use of the set operations to combine them as desired.

Utils

It is possible to get an utils object from the Interval Set class and use it to generate standard interval sets:

import clothesline
uti = clothesline.RealIntervalSet.utils()

uset0 = uti.empty()                       # {}
uset1 = uti.open(2, 3)                    # (2, 3)
uset2 = uti.closed(4, 5)                  # [4, 5]
uset3 = uti.point(6)                      # [6, 6]
uset4 = uti.low_slice(7)                  # (-inf, 7)
uset5 = uti.high_slice(8, included=True)  # [8, +inf)
uset6 = uti.all()                         # (-inf, +inf)
uset7 = uti.interval(9, False, 10, True)  # (9, 10]

The last and most general method (interval()) requires specifying, for the begin and end value, whether the point itself is included.

Constructor

Interval Sets can be created with the class constructor: newIntervalSet = RealIntervalSet([interval1, interval2, ...]), which may be more amenable to programmatic approaches. The provided Intervals, in turn, can be built in three ways:

  • with a builder obtained from an Interval class;

  • with an utils obtained from an Interval class;

  • through the explicit constructor of the Interval class itself

Note that the builder and the utils obtained from Intervals, while having essentially the same behaviour as those from Interval Sets, will always return instances of the appropriate Interval class. (Note: the Interval utils lacks the empty() method.)

When creating an Interval through its constructor, one must provide two instances of the IntervalPeg class (which represents a domain value and a boolean to express whether included in the interval or excluded). In case infinities are involved, these are to be explicitly imported and used as symbols as seen above.

The following code snippet exemplifies the techniques described above:

from clothesline import RealIntervalSet
from clothesline.real_interval import RealInterval
from clothesline.interval_peg import IntervalPeg
#
rbld = RealInterval.builder()
ruti = RealInterval.utils()

int1 = rbld[3](5)
#
int2 = ruti.open(10, 20)
#
peg1 = IntervalPeg(4, False)
peg2 = IntervalPeg(10, True)
int3 = RealInterval(peg1, peg2)
##
intervalset = RealIntervalSet([int1, int2, int3])   # [3, 20)

Note

While there are separate Interval and Interval Set classes for each domain (e.g. real numbers vs. datetimes), the IntervalPeg class is universal. As a consequence, one does not have to subclass it when building an extension to a different domain.

Infinities

Infinities can be specified as explicit values:

import clothesline
from clothesline.algebra.symbols import PlusInf, MinusInf
from clothesline.interval_peg import IntervalPeg
uti = clothesline.RealIntervalSet.utils()
#
my_peg = IntervalPeg(MinusInf, False)
uset8 = uti.interval(9, False, PlusInf, False) # (9, +inf)
er = uti.interval(MinusInf, True, 10, False)   # InvalidValueError!

Infinities are handled automatically in all operations and are always references to (and not “instances of”) two special classes: clothesline.algebra.symbols.MinusInf and clothesline.algebra.symbols.PlusInf.

One should rarely, if at all, concern themselves with the actual nature of infinities in clothesline.

Set operations

Most standard set operations, both unary and binary, are supported between Interval Sets. Union (union method, aliased as +) and difference (difference method, equivalently -) are useful to build more complex Interval Sets starting from the one-interval elements seen so far.

import clothesline
bld = clothesline.RealIntervalSet.builder()
uti = clothesline.RealIntervalSet.utils()

set1 = bld[-1][1]
set2 = bld(0)[2]
set3 = set1 + set2
set3 == set1.union(set2)            # True
set4 = set3.difference(uti.point(0))
set4 == set3 - uti.point(0)         # True
print(set1.xor(set4))
set5 = bld[-5](5)
print(set5.complement())
set5.superset_of(set2)              # True
set5.superset_of(uti.high_slice(0)) # False

Inspection

The method intervals() of an Interval Set returns a (sorted) iterator over all component Intervals (instances of the appropriate Interval class). Each of these, in turn, exposes an iterator over its two pegs (start and end, in that order), whose properties value and included can be accessed for any further use.

If needed, moreover, package clothesline.algebra.symbols offers tools to work with a domain in a way that is friendly with the MinusInf and PlusInf objects.

import clothesline
bld = clothesline.RealIntervalSet.builder()
set1 = bld[...](-5) + bld[0][10] - bld[2](4)

for int in set1.intervals():
    begin, end = list(int.pegs())
    print('From %s (%s) ' % (begin.value, begin.included), end='')
    print('to %s (%s)' % (end.value, end.included))

from clothesline.algebra.symbols import is_symbol
all_pegs = (peg for int in set1.intervals() for peg in int.pegs())
if any(is_symbol(peg.value) for peg in all_pegs):
    print('Infinities involved!')

Hashability

Methods __eq__() and __hash__() are implemented, which enables usage of Interval Sets (as well as Intervals) as elements of standard Python sets or keys of dicts, for instance. Moreover, since the internal representation of Interval Sets is always normalized to a canonical form, sets that are “mathematically equal” will always evaluate to the same Python hash (and yield True under the == comparison).

Metric

Most domains, such as real numbers, are equipped with a metric, i.e. a way to determine the “extent” of an interval.

import clothesline
bld = clothesline.RealIntervalSet.builder()

bld[0](10).extension()      # 10
bld(...)[0].extension()
    # <class 'clothesline.algebra.symbols.PlusInf'>
(bld[1][2] + bld[6][8]).extension() # 3

Serializability

The clothesline package does not directly provide serialization/deserialization facilities, thus leaving maximum flexibility to the user: what it does, instead, is to provide a JSON-friendly dict representation for its objects, that can then be dumped, stored and loaded wherever it is seen fit using e.g. the JSON format.

import clothesline
bld = clothesline.RealIntervalSet.builder()
uti = clothesline.RealIntervalSet.utils()

set1 = bld[0](3) + bld(5)(8)
set2 = bld[...](-1) - bld[-3][-2]

dset1 = set1.to_dict()
dset2 = set2.to_dict()

import json

jset1 = json.dumps(dset1)   # this is a String
jset2 = json.dumps(dset2)   # this is a String

set1 == uti.from_dict(json.loads(jset1))    # True
set2 == uti.from_dict(json.loads(jset2))    # True

Datetime

Support for datetime-based interval sets is ready to use: class DatetimeIntervalSet supports everything that has been shown so far.

import clothesline
from datetime import datetime
bld = clothesline.DatetimeIntervalSet.builder()
uti = clothesline.DatetimeIntervalSet.utils()

tset1 = bld[datetime(1999, 12, 31)][...]
tset2 = bld[datetime(2001, 1, 1)](...)

tset3 = bld[datetime(2022, 1, 1)](datetime(2023, 1, 1))
print(tset3.extension())  # 365 days, 0:00:00

import json
jtset1 = json.dumps(tset1.to_dict())
tset1 == uti.from_dict(json.loads(jtset1))  # True

Warning

It is unwise, and not supported, to mix Interval Sets built on different domains.