diff --git a/.gitignore b/.gitignore index 43d92ee10bec5ebd233964e5af74e6d5d431fa50..b6659a698d711f6b6af858c2308ce3a2f6821277 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,6 @@ instance/ # Scrapy stuff: .scrapy -docs/ # Sphinx documentation docs/_build/ diff --git a/README.md b/README.md index 734d63f54b8df5bc9097971599859a5fa059d9b7..42482b2bbe73ea293996d8d570f4076ac6bf6018 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ slice them and so on. ## Series +Can be imported from _sai.series_. A generic abstract superclass for series - +`Series` can be imported for checking if given object is a series. + ### DiscreteSeries To use a _DiscreteSeries_ you must give it a set of data to work with. These @@ -57,6 +60,17 @@ fs[6] == 6 Although you can't specify a domain where it would be impossible to compute the value. (ie. starting at smaller than zero). Doing so will throw a _ValueError_. +Note that when using `join_discrete()` sometimes other series might get calls +from beyond their domain. This can be seen for example here: + +```python +logs = FunctionSeries(math.log, '(0;5>') +dirs = DiscreteSeries([(0,1)], '<0;5>') + +# Raises ValueError due to math.log being called with 0 +dirs.join_discrete(logs, lambda x, y: x+y) +``` + ### FunctionSeries Using _FunctionSeries_ is straightforward. Just give them a callable and @@ -66,12 +80,23 @@ a domain: fs = FunctionSeries(lambda x: x**2, '<-2;2>') ``` -## Ranges +### ModuloSeries + +_ModuloSeries_ allow you to wrap a finite series in repetition. ```python -from firanka.series import Range +fs = ModuloSeries(someOtherSeries) ``` +By definition, _ModuloSeries_ has the domain of all real numbers. + +Note that someOtherSeries's domain length must be non-zero and finite. Otherwise +_ValueError_ will be thrown. + +## Ranges + +Can be imported from _sai.series_. + Range would have been better called an **interval**. It is a continuous subset of the real number line. diff --git a/firanka/series/series.py b/firanka/series/series.py index 8cdc7133016c0c43dc333ea5d2103ac9be8e5c72..d5847d2900b710274129ea768d0b96421e88e70d 100644 --- a/firanka/series/series.py +++ b/firanka/series/series.py @@ -2,8 +2,7 @@ # coding=UTF-8 from __future__ import print_function, absolute_import, division import six -import functools -import itertools +import math from .range import Range, REAL_SET, EMPTY_RANGE from .exceptions import NotInDomainError @@ -153,7 +152,6 @@ class DiscreteSeries(Series): if self.domain.start < data[0][0]: raise ValueError('some domain space is not covered by definition!') - def apply(self, fun): return DiscreteSeries([(k, fun(v)) for k, v in self.data], self.domain) @@ -265,6 +263,11 @@ class JoinedSeries(Series): class ModuloSeries(Series): def __init__(self, series): + """ + Construct a modulo series + :param series: base series to use + :raise ValueError: invalid domain length + """ super(ModuloSeries, self).__init__(REAL_SET) self.series = series @@ -272,6 +275,8 @@ class ModuloSeries(Series): if self.period == 0: raise ValueError('Modulo series cannot have a period of 0') + elif math.isinf(self.period): + raise ValueError('Modulo series cannot have an infinite period') def _get_for(self, item): if item < 0: diff --git a/tests/test_series/test_series.py b/tests/test_series/test_series.py index 0bc2bb3553dda3b2976078b568b03e13dd2af2f5..219a56c1696d39ce2f850eb51f200a1c15435b3b 100644 --- a/tests/test_series/test_series.py +++ b/tests/test_series/test_series.py @@ -1,9 +1,11 @@ # coding=UTF-8 from __future__ import print_function, absolute_import, division import six +import math import unittest from firanka.series import DiscreteSeries, FunctionSeries, Range, ModuloSeries, NotInDomainError +NOOP = lambda x: x class TestDiscreteSeries(unittest.TestCase): @@ -63,7 +65,7 @@ class TestDiscreteSeries(unittest.TestCase): def test_eval2(self): sa = DiscreteSeries([[0, 0], [1, 1], [2, 2]]) - sb = FunctionSeries(lambda x: x, '<0;2>') + sb = FunctionSeries(NOOP, '<0;2>') sc = sa.join_discrete(sb, lambda a, b: a+b) self.assertEqual(sc.eval_points([0,1,2]), [0,2,4]) @@ -77,7 +79,7 @@ class TestDiscreteSeries(unittest.TestCase): def test_eval3(self): sa = FunctionSeries(lambda x: x**2, '<-10;10)') - sb = FunctionSeries(lambda x: x, '<0;2)') + sb = FunctionSeries(NOOP, '<0;2)') sc = sa.join(sb, lambda a, b: a*b) @@ -104,9 +106,10 @@ class TestDiscreteSeries(unittest.TestCase): empty = FunctionSeries(lambda x: x**2, '<-10;10)').discretize([]) self.assertTrue(empty.domain.is_empty()) + class TestFunctionSeries(unittest.TestCase): def test_slice(self): - series = FunctionSeries(lambda x: x, '<0;2>') + series = FunctionSeries(NOOP, '<0;2>') sp = series[0.5:1.5] self.assertEqual(sp[0.5], 0.5) @@ -118,11 +121,23 @@ class TestFunctionSeries(unittest.TestCase): def test_apply(self): PTS = [-1,-2,-3,1,2,3] - series = FunctionSeries(lambda x: x, '<-5;5>').apply(lambda x: x*2) + series = FunctionSeries(NOOP, '<-5;5>').apply(lambda x: x*2) self.assertEqual(series.eval_points(PTS), [x*2 for x in PTS]) + def test_domain_sensitivity(self): + logs = FunctionSeries(math.log, '(0;5>') + dirs = DiscreteSeries([(0,1),(1,2),(3,4)], '<0;5>') + + self.assertRaises(ValueError, lambda: dirs.join_discrete(logs, lambda x, y: x+y)) + class TestModuloSeries(unittest.TestCase): + + def test_exceptions(self): + self.assertRaises(ValueError, lambda: ModuloSeries(FunctionSeries(NOOP, '(-inf; 0>'))) + self.assertRaises(ValueError, lambda: ModuloSeries(FunctionSeries(NOOP, '(-inf; inf)'))) + self.assertRaises(ValueError, lambda: ModuloSeries(FunctionSeries(NOOP, '<0; 0>'))) + def test_base(self): series = ModuloSeries(DiscreteSeries([(0,1),(1,2),(2,3)], '<0;3)')) @@ -133,7 +148,7 @@ class TestModuloSeries(unittest.TestCase): def test_comp_discrete(self): ser1 = ModuloSeries(FunctionSeries(lambda x: x**2, '<0;3)')) - ser2 = FunctionSeries(lambda x: x, '<0;3)') + ser2 = FunctionSeries(NOOP, '<0;3)') ser3 = ser1.join(ser2, lambda x, y: x*y)