Portion Library for Python Intervals
The portion library (current version 2.6.1) provides a robust data structure and operations for intervals in Python. It supports various types of intervals (closed, open, finite, semi-infinite) with any comparable objects, including interval sets (disjunctions of atomic intervals). The library offers automatic simplification, comparison, transformation, intersection, union, complement, difference, containment, and discrete iteration, along with a dict-like structure (`IntervalDict`) to map intervals to data. It is actively maintained with regular releases.
Warnings
- breaking In version 2.3.0, the representation and comparison of the empty interval changed. `list(P.empty())` now correctly returns `[]` (an empty list) instead of `[P.empty()]`. Additionally, the empty interval is no longer considered less than, greater than, less than or equal to, or greater than or equal to any other interval for consistency.
- breaking Version 2.0.0 introduced significant breaking changes, including the renaming of the library from `python-intervals` to `portion`. Many optional parameters became keyword-only arguments. Key methods like `is_empty()`, `is_atomic()`, and `to_atomic()` were removed in favor of attributes (`.empty`, `.atomic`) or other methods (`.enclosure`), and `AtomicInterval` was removed from the public API.
- deprecated Direct comparison between an interval and a scalar value (e.g., `interval <= value` or `value >= interval`) is deprecated since version 2.3.0 as it is ill-defined. This applies when the scalar is on the left side of a comparison operator.
- gotcha Official support for older Python versions has been progressively dropped: Python 3.8 support was dropped in 2.6.1, 3.7 in 2.5.0, and 3.6 in 2.3.1. The `portion` library now officially requires Python 3.9 or newer.
- gotcha The experimental `create_api` function experienced import errors in versions 2.4.1 and 2.4.2, particularly when used outside a REPL environment or with Python 3.10+.
Install
-
pip install portion
Imports
- Interval
import portion as P interval = P.Interval([1, 5])
- open, closed, openclosed, closedopen, singleton, empty, inf
import portion as P open_interval = P.open(1, 5) closed_interval = P.closed(1, 5) empty_interval = P.empty() positive_infinity = P.inf
- IntervalDict
import portion as P data_dict = P.IntervalDict()
Quickstart
import portion as P
# Create intervals
i1 = P.closed(1, 5) # [1,5]
i2 = P.open(3, 7) # (3,7)
i3 = P.singleton(10) # [10,10]
i_inf = P.openclosed(-P.inf, 0) # (-inf,0]
print(f"Interval 1: {i1}")
print(f"Interval 2: {i2}")
# Perform operations
union = i1 | i2 # Union
intersection = i1 & i2 # Intersection
difference = i1 - i2 # Difference
complement = ~i1 # Complement (relative to the full domain)
print(f"Union: {union}")
print(f"Intersection: {intersection}")
print(f"Difference: {difference}")
print(f"Complement of [1,5]: {complement}")
# Check properties
print(f"Is i1 in i2? {i2.contains(i1)}") # False
print(f"Does i1 overlap i2? {i1.overlaps(i2)}") # True
print(f"Is empty interval empty? {P.empty().empty}") # True
# Using IntervalDict
id = P.IntervalDict()
id[P.closed(0, 10)] = 'Phase 1'
id[P.open(10, 20)] = 'Phase 2'
id[P.singleton(25)] = 'Event'
print(f"\nIntervalDict: {id}")
print(f"Value at 5: {id[5]}") # 'Phase 1'
print(f"Value at 15: {id[15]}") # 'Phase 2'
print(f"Value at 25: {id[25]}") # 'Event'
# Get items as (interval, value) pairs
print(f"Items: {list(id.items())}")