diff --git a/.gitignore b/.gitignore index b6659a698d711f6b6af858c2308ce3a2f6821277..43d92ee10bec5ebd233964e5af74e6d5d431fa50 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ instance/ # Scrapy stuff: .scrapy +docs/ # Sphinx documentation docs/_build/ diff --git a/README.md b/README.md index 22d5de5c8d7e85ef801afdaf33d70f66c2ba5771..d891ca742832c82346793f0fb91166e6677f87bb 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,57 @@ []() []() -Calculations on continuous, domain-being-a-single-interval, real domain -functions. +firanka is a Python library to perform calculations on particular kinds of +functions. These functions have a domain, which is a single continuous subset +of the real number line. These functions can have any values. -Functions of index: float -> any; +firanka allows you do define two classes of such functions or series. +First are the _DiscreteSeries_. _DiscreteSeries_ further divide the function +domain into slices (left-closed, right-open) that have constant values. +Manipulating _DiscreteSeries_ and performing calculations on them is cheap. +Then you have _FunctionSeries_. These are simply defined by user-supplied +Python callable. -You can: +Best part is, you can join series together (given a joining operator), +slice them and so on. -* join two series with a single operation -* use a function on each value + +# Usage +## Ranges + +```python +from firanka.series import Range +``` + +Range would have been better called an **interval**. It is a continuous subset +of the real number line. + +You can create Ranges as follows: + +```python +Range(-5, 5, True, False) == Range('<-5;5)') +``` + +First boolean argument signifies whether the interval is left-closed, +and second whether it is right-closed. + +Range's are immutable and hashable. They can be sliced: ```python -from firanka.series import DiscreteSeries, FunctionBasedSeries +Range('<-5;5>')[0:] == Range('<0;5>') +``` -ds = DiscreteSeries(list of tuple(index, value), '<-20;4)') +You can check whether a range contains a point + +```python +5 not in Range('<-1;5)') +``` + +Or you can check for strict inclusion + +```python +Range('<-1;1>') in Range('<-2;2>') +``` -``` \ No newline at end of file diff --git a/firanka/series/__init__.py b/firanka/series/__init__.py index ea3c4b2092c52f9f4f2de02c2f306fdc26f74c12..fb30d506acd788a3a4732b98ef5fd2da58ee8fb3 100644 --- a/firanka/series/__init__.py +++ b/firanka/series/__init__.py @@ -5,7 +5,7 @@ import logging from .exceptions import NotInDomainError, FirankaError from .range import Range, REAL_SET -from .series import DiscreteSeries, FunctionBasedSeries, ModuloSeries, Series +from .series import DiscreteSeries, FunctionSeries, ModuloSeries, Series __all__ = [ @@ -13,7 +13,7 @@ __all__ = [ 'FirankaError', 'NotInDomainError', 'Range', - 'FunctionBasedSeries', + 'FunctionSeries', 'DiscreteSeries', 'ModuloSeries', 'Series', diff --git a/firanka/series/range.py b/firanka/series/range.py index 1dc63237a22a231deedc8bac7dcf8bb9499be539..a5e45697951116cf71186d71cd5267970866af8f 100644 --- a/firanka/series/range.py +++ b/firanka/series/range.py @@ -32,7 +32,9 @@ class Range(object): if isinstance(rs, type(self)): args = rs.start, rs.stop, rs.left_inc, rs.right_inc elif isinstance(rs, slice): - args = rs.start, rs.stop, True, True + start = rs.start if rs.start is not None else float('-inf') + stop = rs.stop if rs.stop is not None else float('+inf') + args = start, stop, not math.isinf(start), not math.isinf(stop) else: if rs[0] not in '<(': raise ValueError('Must start with ( or <') if rs[-1] not in '>)': raise ValueError('Must end with ) or >') @@ -84,6 +86,12 @@ class Range(object): def __repr__(self): return 'Range(%s, %s, %s, %s)' % (repr(self.start), repr(self.stop), repr(self.left_inc), repr(self.right_inc)) + def __getitem__(self, item): + if not isinstance(item, slice): + raise ValueError('must be a slice') + + return self.intersection(Range(item)) + def __str__(self): return '%s%s;%s%s' % ( '<' if self.left_inc else '(', diff --git a/firanka/series/series.py b/firanka/series/series.py index 31f15d2b56a7d3969129e5e1772eeae67bbbd6e9..b4a18e7b668c51791e07dc5cf1559c8e3a303005 100644 --- a/firanka/series/series.py +++ b/firanka/series/series.py @@ -230,12 +230,12 @@ class DiscreteSeries(Series): return DiscreteSeries(nd, self.domain) -class FunctionBasedSeries(Series): +class FunctionSeries(Series): """ Series with values defined by a function """ def __init__(self, fun, domain): - super(FunctionBasedSeries, self).__init__(domain) + super(FunctionSeries, self).__init__(domain) self.fun = fun def _get_for(self, item): diff --git a/tests/test_series/test_range.py b/tests/test_series/test_range.py index 068b0b846948ca31cfd0ec8f9aa88c8c7ab63d39..4476b083c6260967955f36e4db92af193146600d 100644 --- a/tests/test_series/test_range.py +++ b/tests/test_series/test_range.py @@ -17,6 +17,9 @@ class TestRange(unittest.TestCase): self.assertEqual(Range(a).intersection(b), Range(val)) self.assertEqual(Range(b).intersection(a), Range(val)) + def test_slicing(self): + self.assertTrue(Range('<-5;5>')[0:] == Range('<0;5>')) + def test_isempty(self): self.assertTrue(Range(-1,-1,False,False).is_empty()) self.assertFalse(Range(-1,-1,False,True).is_empty()) diff --git a/tests/test_series/test_series.py b/tests/test_series/test_series.py index c7451f87dc30218cb728d0874e661db370513a78..f7ea5b1dc7b19f1f48792ee8f8a58a7ae65c794e 100644 --- a/tests/test_series/test_series.py +++ b/tests/test_series/test_series.py @@ -2,7 +2,7 @@ from __future__ import print_function, absolute_import, division import six import unittest -from firanka.series import DiscreteSeries, FunctionBasedSeries, Range, ModuloSeries, NotInDomainError +from firanka.series import DiscreteSeries, FunctionSeries, Range, ModuloSeries, NotInDomainError class TestDiscreteSeries(unittest.TestCase): @@ -60,7 +60,7 @@ class TestDiscreteSeries(unittest.TestCase): def test_eval2(self): sa = DiscreteSeries([[0, 0], [1, 1], [2, 2]]) - sb = FunctionBasedSeries(lambda x: x, '<0;2>') + sb = FunctionSeries(lambda x: x, '<0;2>') sc = sa.join_discrete(sb, lambda a, b: a+b) self.assertEqual(sc.eval_points([0,1,2]), [0,2,4]) @@ -73,8 +73,8 @@ class TestDiscreteSeries(unittest.TestCase): self.assertEquals(sa.data, [(0,1),(1,2),(2,3)]) def test_eval3(self): - sa = FunctionBasedSeries(lambda x: x**2, '<-10;10)') - sb = FunctionBasedSeries(lambda x: x, '<0;2)') + sa = FunctionSeries(lambda x: x**2, '<-10;10)') + sb = FunctionSeries(lambda x: x, '<0;2)') sc = sa.join(sb, lambda a, b: a*b) @@ -86,20 +86,20 @@ class TestDiscreteSeries(unittest.TestCase): def test_discretize(self): PTS = [0,1,2,3,4,5] - sa = FunctionBasedSeries(lambda x: x**2, '<-10;10)').discretize(PTS, '(-1;6)') + sa = FunctionSeries(lambda x: x**2, '<-10;10)').discretize(PTS, '(-1;6)') self.assertIsInstance(sa, DiscreteSeries) self.assertEqual(sa.data, [(i, i**2) for i in PTS]) - sa = FunctionBasedSeries(lambda x: x**2, '<-10;10)').discretize(PTS) + sa = FunctionSeries(lambda x: x**2, '<-10;10)').discretize(PTS) self.assertIsInstance(sa, DiscreteSeries) self.assertEqual(sa.data, [(i, i**2) for i in PTS]) - empty = FunctionBasedSeries(lambda x: x**2, '<-10;10)').discretize([]) + empty = FunctionSeries(lambda x: x**2, '<-10;10)').discretize([]) self.assertTrue(empty.domain.is_empty()) -class TestFunctionBasedSeries(unittest.TestCase): +class TestFunctionSeries(unittest.TestCase): def test_slice(self): - series = FunctionBasedSeries(lambda x: x, '<0;2>') + series = FunctionSeries(lambda x: x, '<0;2>') sp = series[0.5:1.5] self.assertEqual(sp[0.5], 0.5) @@ -111,7 +111,7 @@ class TestFunctionBasedSeries(unittest.TestCase): def test_apply(self): PTS = [-1,-2,-3,1,2,3] - series = FunctionBasedSeries(lambda x: x, '<-5;5>').apply(lambda x: x*2) + series = FunctionSeries(lambda x: x, '<-5;5>').apply(lambda x: x*2) self.assertEqual(series.eval_points(PTS), [x*2 for x in PTS]) @@ -125,8 +125,8 @@ class TestModuloSeries(unittest.TestCase): self.assertEquals(series[-1], 3) def test_comp_discrete(self): - ser1 = ModuloSeries(FunctionBasedSeries(lambda x: x**2, '<0;3)')) - ser2 = FunctionBasedSeries(lambda x: x, '<0;3)') + ser1 = ModuloSeries(FunctionSeries(lambda x: x**2, '<0;3)')) + ser2 = FunctionSeries(lambda x: x, '<0;3)') ser3 = ser1.join(ser2, lambda x, y: x*y)