diff --git a/firanka/__init__.py b/firanka/__init__.py index 2fb25139f1489c1c5f9f939ddc411d187d5c8e75..563f38cda781127c00bd64756e35b54eafe2c350 100644 --- a/firanka/__init__.py +++ b/firanka/__init__.py @@ -1 +1 @@ -__version__ = '0.1.6' +__version__ = '0.1.7rc1' diff --git a/firanka/series/series.py b/firanka/series.py similarity index 83% rename from firanka/series/series.py rename to firanka/series.py index dcc777ae37a3a4ef27b0a182b669daf9eb5536fc..b59d5df57f5e3843b02889f335c56139aa9d3d98 100644 --- a/firanka/series/series.py +++ b/firanka/series.py @@ -9,6 +9,13 @@ import six from firanka.exceptions import NotInDomainError from firanka.ranges import Range, REAL_SET, EMPTY_SET +__all__ = [ + 'FunctionSeries', + 'DiscreteSeries', + 'ModuloSeries', + 'Series', +] + class Series(object): """ @@ -18,10 +25,11 @@ class Series(object): for minimum functionality """ - def __init__(self, domain): + def __init__(self, domain, comment=u''): if not isinstance(domain, Range): domain = Range(domain) self.domain = domain + self.comment = comment def __getitem__(self, item): """ @@ -62,6 +70,14 @@ class Series(object): :param fun: callable/1 => 1 :return: Series instance """ + return AppliedSeries(self, lambda k, v: fun(v)) + + def apply_with_indices(self, fun): + """ + Return this series with a function applied to each value + :param fun: callable(index: float, value: any) => 1 + :return: Series instance + """ return AppliedSeries(self, fun) def discretize(self, points, domain=None): @@ -101,18 +117,19 @@ class Series(object): class AppliedSeries(Series): - def __init__(self, series, applyfun): - super(AppliedSeries, self).__init__(series.domain) + def __init__(self, series, applyfun, *args, **kwargs): + """:type applyfun: callable(float, v) -> any""" + super(AppliedSeries, self).__init__(series.domain, *args, **kwargs) self.fun = applyfun self.series = series def _get_for(self, item): - return self.fun(self.series._get_for(item)) + return self.fun(item, self.series._get_for(item)) class TranslatedSeries(Series): - def __init__(self, series, x): - super(TranslatedSeries, self).__init__(self.domain.translate(x)) + def __init__(self, series, x, *args, **kwargs): + super(TranslatedSeries, self).__init__(self.domain.translate(x), *args, **kwargs) self.series = series self.x = x @@ -121,8 +138,8 @@ class TranslatedSeries(Series): class SlicedSeries(Series): - def __init__(self, parent, domain): - super(SlicedSeries, self).__init__(domain) + def __init__(self, parent, domain, *args, **kwargs): + super(SlicedSeries, self).__init__(domain, *args, **kwargs) self.parent = parent def _get_for(self, item): @@ -141,14 +158,14 @@ def _appendif(lst, ptr, v): class DiscreteSeries(Series): - def __init__(self, data, domain=None): + def __init__(self, data, domain=None, *args, **kwargs): if len(data) == 0: domain = EMPTY_SET elif domain is None: domain = Range(data[0][0], data[-1][0], True, True) self.data = data - super(DiscreteSeries, self).__init__(domain) + super(DiscreteSeries, self).__init__(domain, *args, **kwargs) if len(data) > 0: if self.domain.start < data[0][0]: @@ -157,6 +174,9 @@ class DiscreteSeries(Series): def apply(self, fun): return DiscreteSeries([(k, fun(v)) for k, v in self.data], self.domain) + def apply_with_indices(self, fun): + return DiscreteSeries([(k, fun(k, v)) for k, v in self.data], self.domain) + def _get_for(self, item): for k, v in reversed(self.data): if k <= item: @@ -225,7 +245,6 @@ class DiscreteSeries(Series): return DiscreteSeries(c, new_domain) - def compute(self): """Simplify self""" nd = [self.data[0]] @@ -239,8 +258,8 @@ class FunctionSeries(Series): """ Series with values defined by a function """ - def __init__(self, fun, domain): - super(FunctionSeries, self).__init__(domain) + def __init__(self, fun, domain, *args, **kwargs): + super(FunctionSeries, self).__init__(domain, *args, **kwargs) self.fun = fun def _get_for(self, item): @@ -251,9 +270,9 @@ class JoinedSeries(Series): """ Series stemming from performing an operation on two series """ - def __init__(self, ser1, ser2, op): + def __init__(self, ser1, ser2, op, *args, **kwargs): domain = ser1.domain.intersection(ser2.domain) - super(JoinedSeries, self).__init__(domain) + super(JoinedSeries, self).__init__(domain, *args, **kwargs) self.ser1 = ser1 self.ser2 = ser2 self.op = op @@ -264,13 +283,13 @@ class JoinedSeries(Series): class ModuloSeries(Series): - def __init__(self, series): + def __init__(self, series, *args, **kwargs): """ Construct a modulo series :param series: base series to use :raise ValueError: invalid domain length """ - super(ModuloSeries, self).__init__(REAL_SET) + super(ModuloSeries, self).__init__(REAL_SET, *args, **kwargs) self.series = series self.period = self.series.domain.length() diff --git a/firanka/series/__init__.py b/firanka/series/__init__.py deleted file mode 100644 index 3407ef614f2778c78992bee020fd4f4cbcec5dc6..0000000000000000000000000000000000000000 --- a/firanka/series/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# coding=UTF-8 -from __future__ import print_function, absolute_import, division - -from .series import DiscreteSeries, FunctionSeries, ModuloSeries, Series - -__all__ = [ - 'FunctionSeries', - 'DiscreteSeries', - 'ModuloSeries', - 'Series', -] diff --git a/firanka/timeproviders.py b/firanka/timeproviders.py new file mode 100644 index 0000000000000000000000000000000000000000..91d18a5bfac04382cb566bb54aa61ef50a14c825 --- /dev/null +++ b/firanka/timeproviders.py @@ -0,0 +1,58 @@ +# coding=UTF-8 +from __future__ import print_function, absolute_import, division +import six +import logging + + +from .series import Series +from .ranges import Range + + +class BijectionMapping(object): + + def __init__(self, user2float, float2user): + """ + :param user2float: callable/1 => float, mapping from your time system to floats + :param float2user: callable/float => 1, mapping from float to your time systen + """ + self.user2float = user2float + self.float2user = float2user + + def to_float(self, user): + return self.user2float(user) + + def to_user(self, flt): + return self.float2user(flt) + + +class TimeProvidedSeries(Series): + """ + If your time is something else than simple floats, this will help you out + """ + def __init__(self, series, mapping, *args, **kwargs): + """ + :param series: series to overlay + """ + super(TimeProvidedSeries, self).__init__(series.domain, *args, **kwargs) + self.mapping = mapping + self.series = series + + def _withmap(self, series): + return TimeProvidedSeries(series, self.mapping) + + def __getitem__(self, item): + if isinstance(item, slice): + item = slice(float('-inf') if slice.start is None else self.to_float(slice.start), + float('+inf') if slice.stop is None else self.to_float(slice.stop),) + + return self._withmap(self.series[item]) + elif isinstance(item, Range): + return self._withmap(self.series[item]) + else: + return self.series[self.mapping.to_float(item)] + + def _get_for(self, item): + return self.series[self.l2f(item)] + + def join(self, series, fun): + return TimeProvidedSeries(self.series.join(series, fun), self.mapping) diff --git a/tests/test_series/test_range.py b/tests/test_range.py similarity index 100% rename from tests/test_series/test_range.py rename to tests/test_range.py diff --git a/tests/test_series/test_series.py b/tests/test_series.py similarity index 94% rename from tests/test_series/test_series.py rename to tests/test_series.py index f83a952e478e8a147f0c1b54ae26f0dd29bac45d..1b45267efa8ac2526203d5eadea89286d8ff9244 100644 --- a/tests/test_series/test_series.py +++ b/tests/test_series.py @@ -79,6 +79,9 @@ class TestDiscreteSeries(unittest.TestCase): sa = DiscreteSeries([[0, 0], [1, 1], [2, 2]]).apply(lambda x: x+1) self.assertEquals(sa.data, [(0,1),(1,2),(2,3)]) + sb = DiscreteSeries([[0, 0], [1, 1], [2, 2]]).apply_with_indices(lambda k,v: k) + self.assertEquals(sb.data, [(0,0),(1,1),(2,2)]) + def test_eval3(self): sa = FunctionSeries(lambda x: x**2, '<-10;10)') sb = FunctionSeries(NOOP, '<0;2)') @@ -127,6 +130,11 @@ class TestFunctionSeries(unittest.TestCase): self.assertEqual(series.eval_points(PTS), [x*2 for x in PTS]) + PTS = [-1,-2,-3,1,2,3] + series = FunctionSeries(NOOP, '<-5;5>').apply_with_indices(lambda k, x: k) + + self.assertEqual(series.eval_points(PTS), [x 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>') diff --git a/tests/test_series/__init__.py b/tests/test_series/__init__.py deleted file mode 100644 index 1c762b12fd99adc2f7d4e5137c5b872079457510..0000000000000000000000000000000000000000 --- a/tests/test_series/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# coding=UTF-8 -from __future__ import print_function, absolute_import, division -import six -import logging - -logger = logging.getLogger(__name__) - - diff --git a/tests/test_timeproviders.py b/tests/test_timeproviders.py new file mode 100644 index 0000000000000000000000000000000000000000..eeb784991781e4f58309bf9e5d535f48893085be --- /dev/null +++ b/tests/test_timeproviders.py @@ -0,0 +1,21 @@ +# coding=UTF-8 +from __future__ import print_function, absolute_import, division +import six +import unittest + +from firanka.series import DiscreteSeries +from firanka.timeproviders import TimeProvidedSeries, BijectionMapping + + +class TestTimeproviders(unittest.TestCase): + + def test_base(self): + map = BijectionMapping( + lambda hhmm: hhmm[0] * 60 + hhmm[1], + lambda t: (t // 60, t % 60) + ) + + ser = DiscreteSeries([(0,17), (60, 20), (120, 18)]) + ts = TimeProvidedSeries(ser, map) + + self.assertEqual(ts[(2, 0)], 18)