Interval arithmetic for Python
Project description
Interval arithmetic for Python
This library provides interval arithmetic for Python 2.7+ and Python 3.4+.
Features
- Support intervals of any (comparable) objects.
- Closed or open, finite or infinite intervals.
- Atomic intervals and interval sets are supported.
- Automatic simplification of intervals.
- Support iteration, comparison, intersection, union, complement, difference and containment.
Installation
You can use pip to install it, as usual: pip install python-intervals.
This will install the latest available version from PyPI. Prereleases are available from its master branch on GitHub.
For convenience, the library is contained within a single Python file, and can thus be easily integrated in other projects without the need for an explicit dependency (hint: don't do that!).
Documentation & usage
Interval creation
Assuming this library is imported using import intervals as I, intervals can be easily created using one of the
following helpers:
>>> I.open(1, 2)
(1,2)
>>> I.closed(1, 2)
[1,2]
>>> I.openclosed(1, 2)
(1,2]
>>> I.closedopen(1, 2)
[1,2)
>>> I.singleton(1)
[1]
>>> I.empty()
()
Intervals created with this library are Interval instances.
An Interval object is a disjunction of atomic intervals that represent single intervals (e.g. [1,2]) corresponding to AtomicInterval instances.
Except when atomic intervals are explicitly created or retrieved, only Interval instances are exposed
The bounds of an interval can be any arbitrary values, as long as they are comparable:
>>> I.closed(1.2, 2.4)
[1.2,2.4]
>>> I.closed('a', 'z')
['a','z']
>>> from datetime import date
>>> I.closed(date(2011, 3, 15), date(2013, 10, 10))
[datetime.date(2011, 3, 15),datetime.date(2013, 10, 10)]
Infinite and semi-infinite intervals are supported using I.inf and -I.inf as upper or lower bounds.
These two objects support comparison with any other object.
When infinites are used as a lower or upper bound, the corresponding boundary is automatically converted to an open one.
>>> I.inf > 'a', I.inf > 0, I.inf > True
(True, True, True)
>>> I.openclosed(-I.inf, 0)
(-inf,0]
>>> I.closed(-I.inf, I.inf) # Automatically converted to an open interval
(-inf,+inf)
Empty intervals always resolve to (I.inf, -I.inf), regardless of the provided bounds:
>>> I.empty() == I.open(I.inf, -I.inf)
True
>>> I.closed(4, 3) == I.open(I.inf, -I.inf)
True
>>> I.openclosed('a', 'a') == I.open(I.inf, -I.inf)
True
For convenience, intervals are automatically simplified:
>>> I.closed(0, 2) | I.closed(2, 4)
[0,4]
>>> I.closed(1, 2) | I.closed(3, 4) | I.closed(2, 3)
[1,4]
>>> I.empty() | I.closed(0, 1)
[0,1]
>>> I.closed(1, 2) | I.closed(2, 3) | I.closed(4, 5)
[1,3] | [4,5]
Note that discrete intervals are not supported, e.g., combining [0,1] with [2,3] will not result
in [0,3] even if there is no integer between 1 and 2.
Arithmetic operations
Both Interval and AtomicInterval support following interval arithmetic operations:
-
x.is_empty()tests if the interval is empty.>>> I.closed(0, 1).is_empty() False >>> I.closed(0, 0).is_empty() False >>> I.openclosed(0, 0).is_empty() True >>> I.empty().is_empty() True
-
x.intersection(other)orx & otherreturn the intersection of two intervals.>>> I.closed(0, 2) & I.closed(1, 3) [1,2] >>> I.closed(0, 4) & I.open(2, 3) (2,3) >>> I.closed(0, 2) & I.closed(2, 3) [2] >>> I.closed(0, 2) & I.closed(3, 4) ()
-
x.union(other)orx | otherreturn the union of two intervals.>>> I.closed(0, 1) | I.closed(1, 2) [0,2] >>> I.closed(0, 1) | I.closed(2, 3) [0,1] | [2,3]
-
x.complement(other)or~xreturn the complement of the interval.>>> ~I.closed(0, 1) (-inf,0) | (1,+inf) >>> ~(I.open(-I.inf, 0) | I.open(1, I.inf)) [0,1] >>> ~I.open(-I.inf, I.inf) ()
-
x.difference(other)orx - otherreturn the difference betweenxandother.>>> I.closed(0,2) - I.closed(1,2) [0,1) >>> I.closed(0, 4) - I.closed(1, 2) [0,1) | (2,4]
-
x.contains(other)orother in xreturn True if given item is contained in the interval. SupportInterval,AtomicIntervaland arbitrary comparable values.>>> 2 in I.closed(0, 2) True >>> 2 in I.open(0, 2) False >>> I.open(0, 1) in I.closed(0, 2) True
-
x.overlaps(other)tests if there is an overlap between two intervals. This method accepts apermissiveparameter which defaults toFalse. IfTrue, it considers that [1, 2) and [2, 3] have an overlap on 2 (but not [1, 2) and (2, 3]).>>> I.closed(1, 2).overlaps(I.closed(2, 3)) True >>> I.closed(1, 2).overlaps(I.open(2, 3)) False >>> I.closed(1, 2).overlaps(I.open(2, 3), permissive=True) True
Other methods and attributes
The following methods are only available for Interval instances:
-
x.enclosure()returns the smallest interval that includes the current one.>>> (I.closed(0, 1) | I.closed(2, 3)).enclosure() [0,3]
-
x.to_atomic()is equivalent tox.enclosure()but returns anAtomicIntervalinstead of anIntervalobject. -
x.is_atomic()evaluates toTrueif interval is composed of a single (possibly empty) atomic interval.>>> I.closed(0, 2).is_atomic() True >>> (I.closed(0, 1) | I.closed(1, 2)).is_atomic() True >>> (I.closed(0, 1) | I.closed(2, 3)).is_atomic() False
The left and right boundaries, and the lower and upper bound of an AtomicInterval can be respectively accessed
with its left, right, lower and upper attributes.
The left and right bounds are either I.CLOSED (True) or I.OPEN (False).
>> I.CLOSED, I.OPEN
True, False
>>> x = I.closedopen(0, 1).to_atomic()
>>> x.left, x.lower, x.upper, x.right
(True, 0, 1, False)
Comparison operators
Equality between intervals can be checked using the classical == operator:
>>> I.closed(0, 2) == I.closed(0, 1) | I.closed(1, 2)
True
>>> I.closed(0, 2) == I.closed(0, 2).to_atomic()
True
Moreover, both Interval and AtomicInterval are comparable using e.g. >, >=, < or <=.
The comparison is based on the interval itself, not on its lower or upper bound only.
For instance, a < b holds if a is entirely on the left of b and a > b holds if a is entirely
on the right of b.
>>> I.closed(0, 1) < I.closed(2, 3)
True
>>> I.closed(0, 1) < I.closed(1, 2)
False
Similarly, a <= b holds if a is entirely on the left of the upper bound of b, and a >= b
holds if a is entirely on the right of the lower bound of b.
>>> I.closed(0, 1) <= I.closed(2, 3)
True
>>> I.closed(0, 2) <= I.closed(1, 3)
True
>>> I.closed(0, 3) <= I.closed(1, 2)
False
Note that this semantics differ from classical comparison operators. As a consequence, some intervals are never comparable in the classical sense, as illustrated hereafter:
>>> I.closed(0, 4) <= I.closed(1, 2) or I.closed(0, 4) >= I.closed(1, 2)
False
>>> I.closed(0, 4) < I.closed(1, 2) or I.closed(0, 4) > I.closed(1, 2)
False
>>> I.empty() < I.empty()
True
Iteration & indexing
Intervals can be iterated to access the underlying AtomicInterval objects, sorted by their lower and upper bounds.
>>> list(I.open(2, 3) | I.closed(0, 1) | I.closed(21, 24))
[[0,1], (2,3), [21,24]]
The AtomicInterval objects of an Interval can also be accessed using their indexes:
>>> (I.open(2, 3) | I.closed(0, 1) | I.closed(21, 24))[0]
[0,1]
>>> (I.open(2, 3) | I.closed(0, 1) | I.closed(21, 24))[-2]
(2,3)
Import and export to string
Intervals can be exported to string, either using repr (as illustrated above) or with the to_string function.
>>> I.to_string(I.closedopen(0, 1))
'[0,1)'
This function accepts both Interval and AtomicInterval instances.
The way string representations are built can be easily parametrized using the various parameters supported by
to_string:
>>> x = I.openclosed(0, 1) | I.closed(2, I.inf)
>>> params = {
... 'disj': ' or ',
... 'sep': ' - ',
... 'left_closed': '<',
... 'right_closed': '>',
... 'left_open': '..',
... 'right_open': '..',
... 'pinf': '+oo',
... 'ninf': '-oo',
... 'conv': lambda v: '"{}"'.format(v),
... }
>>> I.to_string(x, **params)
'.."0" - "1"> or <"2" - +oo..'
Similarly, intervals can be created from a string using the from_string function.
A conversion function (conv parameter) has to be provided to convert a bound (as string) to a value.
>>> I.from_string('[0, 1]', conv=int) == I.closed(0, 1)
True
>>> I.from_string('[1.2]', conv=float) == I.singleton(1.2)
True
>>> from datetime import datetime
>>> converter = lambda s: datetime.strptime(s, '%Y/%m/%d')
>>> I.from_string('[2011/03/15, 2013/10/10]', conv=converter)
[datetime.datetime(2011, 3, 15, 0, 0),datetime.datetime(2013, 10, 10, 0, 0)]
Similarly to to_string, function from_string can be parametrized to deal with more elaborated inputs.
Notice that as from_string expects regular expression patterns, we need to escape some characters.
>>> s = '.."0" - "1"> or <"2" - +oo..'
>>> params = {
... 'disj': ' or ',
... 'sep': ' - ',
... 'left_closed': '<',
... 'right_closed': '>',
... 'left_open': r'\.\.', # from_string expects regular expression patterns
... 'right_open': r'\.\.', # from_string expects regular expression patterns
... 'pinf': r'\+oo', # from_string expects regular expression patterns
... 'ninf': '-oo',
... 'conv': lambda v: int(v[1:-1]),
... }
>>> I.from_string(s, **params)
(0,1] | [2,+inf)
When a bound contains a comma or has a representation that cannot be automatically parsed with from_string,
the bound parameter can be used to specify the regular expression that should be used to match its representation.
>>> s = '[(0, 1), (2, 3)]' # Bounds are expected to be tuples
>>> I.from_string(s, conv=eval, bound=r'\(.+?\)')
[(0, 1),(2, 3)]
Contributions
Contributions are very welcome! Feel free to report bugs or suggest new features using GitHub issues and/or pull requests.
Licence
Distributed under LGPLv3 - GNU Lesser General Public License, version 3.
Changelog
This library adheres to a semantic versioning scheme.
1.6.0 (2018-08-29)
- Add support for customized infinity representation in
to_stringandfrom_string(#3).
1.5.4 (2018-07-29)
- Fix
.overlaps(#2).
1.5.3 (2018-06-21)
- Fix invalid
reprfor atomic singleton intervals.
1.5.2 (2018-06-15)
- Fix invalid comparisons when both
IntervalandAtomicIntervalare compared.
1.5.1 (2018-04-25)
- Fix #1 by making empty intervals always resolving to
(I.inf, -I.inf).
1.5.0 (2018-04-17)
Interval.__init__acceptsIntervalinstances in addition toAtomicIntervalones.
1.4.0 (2018-04-17)
- Function
I.to_stringto export an interval to a string, with many options to customize the representation. - Function
I.from_stringto create an interval from a string, with many options to customize the parsing.
1.3.2 (2018-04-13)
- Support for Python 2.7.
1.3.1 (2018-04-12)
- Define
__slots__to lower memory usage, and to speed up attribute access. - Define
Interval.__rand__(and other magic methods) to supportIntervalfromAtomicIntervalinstead of having a dedicated piece of code inAtomicInterval. - Fix
__all__. - More tests to cover all comparisons.
1.3.0 (2018-04-04)
- Meaningful
<=and>=comparisons for intervals.
1.2.0 (2018-04-04)
Intervalsupports indexing to retrieve the underlyingAtomicIntervalobjects.
1.1.0 (2018-04-04)
- Both
AtomicIntervalandIntervalare fully comparable. - Add
singleton(x)to create a singleton interval [x]. - Add
empty()to create an empty interval. - Add
Interval.enclosure()that returns the smallest interval that includes the current one. - Interval simplification is in O(n) instead of O(n*m).
AtomicIntervalobjects in anIntervalare sorted by lower and upper bounds.
1.0.4 (2018-04-03)
- All operations of
AtomicInterval(except overlaps) acceptInterval. - Raise
TypeErrorinstead ofValueErrorif type is not supported (coherent withNotImplemented).
1.0.3 (2018-04-03)
- Initial working release on PyPi.
1.0.0 (2018-04-03)
- Initial release.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python-intervals-1.6.0.tar.gz.
File metadata
- Download URL: python-intervals-1.6.0.tar.gz
- Upload date:
- Size: 21.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.25.0 CPython/3.5.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a83e3a58dcfb10217a96ebe2dc8600e27f8f519f2d451eef3aa88b50316f06a
|
|
| MD5 |
18f6fecbb860d46cceb2f6487f39960e
|
|
| BLAKE2b-256 |
1916b372f85c75993f92bf85fde639d739fc5d41f755f3791eaf6971a7a44187
|
File details
Details for the file python_intervals-1.6.0-py2.py3-none-any.whl.
File metadata
- Download URL: python_intervals-1.6.0-py2.py3-none-any.whl
- Upload date:
- Size: 10.7 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.25.0 CPython/3.5.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cfada668b389de537ec2abf7f6bead1948b797b7afeaf516b9be91ca3e9a7508
|
|
| MD5 |
2b486fdbc98c457642fea789d7886a56
|
|
| BLAKE2b-256 |
07b59833c0a166a11a30c6942a657279cfa345a692afe3bf4281ed3ded462d05
|