===============
Getting Started
===============
The :class:`~roaringrel.Rel` class implements a low-level mutable data structure to store a finite relation between finite sets:
.. math::
R \subseteq X_1 \times ... \times X_n
It presumes that the *component sets* :math:`X_1,...,X_n` are finite zero-based contiguous integer ranges, in the form :math:`X_j = \lbrace 0,...,s_j-1\rbrace`.
The tuple :math:`(s_1,...,s_n)` of component set sizes is referred to as the *shape* of the relation :math:`R`,
while the tuples :math:`(x_1,...x_n) \in R` are referred to as its *entries*.
Relations are implemented using 64-bit `roaring bitmaps `_ to store the underlying set of entries.
Install
=======
You can install the latest release from `PyPI `_ as follows:
.. code-block:: console
$ pip install roaringrel
Usage
=====
All functionality of the library is accessible from the :class:`~roaringrel.Rel` class:
>>> from roaringrel import Rel
You can create a relation by specifying a shape for it, as a tuple of positive integers:
>>> Rel((2, 3, 4))
The code above creates a ternary relation :math:`R`, initially empty:
.. math::
R \subseteq \lbrace 0, 1 \rbrace \times \lbrace 0, 1, 2 \rbrace \times \lbrace 0, 1, 2, 3 \rbrace
You can also specify initial entries for the relation at creation:
>>> Rel((2, 3, 4), [(0, 0, 0), (1, 1, 1), (1, 2, 3)])
The code above creates a ternary relation :math:`R`, initially with the given three entries:
.. math::
R = \lbrace (0,0,0), (1,1,1), (1,2, 3) \rbrace \subseteq \lbrace 0, 1 \rbrace \times \lbrace 0, 1, 2 \rbrace \times \lbrace 0, 1, 2, 3 \rbrace
Relations are sized iterable containers of entries:
>>> r = Rel((2, 3, 4), [(0, 0, 0), (1, 1, 1), (1, 2, 3)])
>>> len(r)
3
>>> list(r)
[(0, 0, 0), (1, 1, 1), (1, 2, 3)]
>>> (0, 0, 0) in r
True
Relations support basic binary operators as sets of entries, restricted to operations having the same shape:
>>> a = Rel((2, 3, 4), [(0, 0, 0), (1, 2, 3)])
>>> b = Rel((2, 3, 4), [(0, 0, 0), (1, 1, 1)])
>>> a&b # intersection
>>> list(a&b)
[(0, 0, 0)]
>>> a|b # union
>>> list(a|b)
>>> a^b # symmetric difference
>>> list(a^b)
[(1, 1, 1), (1, 2, 3)]
>>> a-b # difference
>>> list(a-b)
[(1, 2, 3)]
Relations support comparison as sets of entries, with subset comparison restricted to relations having the same shape:
>>> a == b # equality comparison
False
>>> a&b <= b # subset comparison
True
>>> a&b < b # strict subset comparison
True
Relations are mutable, with support for addition, removal and flipping of individual elements:
>>> a = Rel((2, 3, 4), [(0, 0, 0), (1, 2, 3)])
>>> a.add((1, 1, 1)) # add entry
>>> list(a)
[(0, 0, 0), (1, 1, 1), (1, 2, 3)]
>>> a.remove((1, 2, 3)) # remove entry
>>> list(a)
[(0, 0, 0), (1, 1, 1)]
>>> a.flip((0, 0, 0)) # flip on existing entry: remove
>>> list(a)
[(1, 1, 1)]
>>> a.flip((0, 0, 0)) # flip on missing entry: add
>>> list(a)
[(0, 0, 0), (1, 1, 1)]
>>> a.remove((1, 2, 3))
KeyError: (1, 2, 3)
The update operations enable addition, removal and flipping (symmetric difference) of multiple entries at once:
>>> a = Rel((2, 3, 4), [(0, 0, 0), (1, 2, 3)])
>>> a.update([(1, 1, 1), (1, 2, 2)]) # add multiple entries
>>> list(a)
[(0, 0, 0), (1, 1, 1), (1, 2, 2), (1, 2, 3)]
>>> a.difference_update([(0, 0, 0), (0, 1, 1)]) # remove multiple entries
>>> list(a)
[(1, 1, 1), (1, 2, 2), (1, 2, 3)]
>>> a.symmetric_difference_update([(1, 2, 3), (0, 1, 1)]) # flip multiple entries
>>> list(a)
[(0, 1, 1), (1, 1, 1), (1, 2, 2)]
Relations support inplace versions of the binary operation, mutating the lhs relation:
>>> a = Rel((2, 3, 4), [(0, 0, 0), (1, 2, 3)])
>>> b = Rel((2, 3, 4), [(0, 0, 0), (1, 1, 1)])
>>> a ^= b
>>> list(a)
[(1, 1, 1), (1, 2, 3)]
>>> a &= b
>>> list(a)
[(1, 1, 1)]
>>> a |= b
>>> list(a)
[(0, 0, 0), (1, 1, 1)]
>>> a -= b
>>> list(a)
[]
The :meth:`~roaringrel.Rel.copy` method can be used to obtain an independently mutable copy of a relation:
>>> r = Rel((2, 3, 4), [(0, 0, 0), (1, 1, 1), (1, 2, 3)])
>>> s = r.copy()
>>> r == s
True
>>> list(r)
[(0, 0, 0), (1, 1, 1), (1, 2, 3)]
>>> list(s)
[(0, 0, 0), (1, 1, 1), (1, 2, 3)]
>>> r.remove((0, 0, 0))
>>> r == s
False
>>> list(r)
[(1, 1, 1), (1, 2, 3)]
>>> list(s)
[(0, 0, 0), (1, 1, 1), (1, 2, 3)]