diff --git a/firanka/series/__init__.py b/firanka/series/__init__.py
index ce1102ae4ee89f9bfb4f99537480d04660c06230..63374d300700521420ba7dca1348c165c7c06385 100644
--- a/firanka/series/__init__.py
+++ b/firanka/series/__init__.py
@@ -4,14 +4,16 @@ import six
 import logging
 
 from .exceptions import OutOfRangeError, EmptyDomainError
-from .range import Range
-from .series import DiscreteSeries, FunctionBasedSeries
+from .range import Range, REAL_SET
+from .series import DiscreteSeries, FunctionBasedSeries, ModuloSeries
 
 
 __all__ = [
+    'REAL_SET',
     'OutOfRangeError',
     'EmptyDomainError',
     'Range',
     'FunctionBasedSeries',
-    'DiscreteSeries'
+    'DiscreteSeries',
+    'ModuloSeries',
 ]
diff --git a/firanka/series/range.py b/firanka/series/range.py
index 2fdc599bccf80b3d779960dd3ea9306ef72749ee..dc1e4a1c110aa8d4f0db7fa3ffee4d97e1b71fb2 100644
--- a/firanka/series/range.py
+++ b/firanka/series/range.py
@@ -23,6 +23,9 @@ class Range(object):
     Range of real numbers. Immutable.
     """
 
+    def translate(self, x):
+        return Range(self.start+x, self.stop+x, self.left_inc, self.right_inc)
+
     def __init__(self, *args):
         if len(args) == 1:
             rs, = args
@@ -76,7 +79,7 @@ class Range(object):
     def is_empty(self):
         return (self.start == self.stop) and not (self.left_inc or self.right_inc)
 
-    def __len__(self):
+    def length(self):
         return self.stop - self.start
 
     def __repr__(self):
@@ -130,4 +133,5 @@ class Range(object):
         return hash(self.start) ^ hash(self.stop)
 
 
-EMPTY_RANGE = Range(0, 0, False, False)
\ No newline at end of file
+EMPTY_RANGE = Range(0, 0, False, False)
+REAL_SET = Range(float('-inf'), float('+inf'), False, False)
diff --git a/firanka/series/series.py b/firanka/series/series.py
index 142dae2ef0aef55e896ff6fb72b75aaf48c3e680..cad10115e2e5fa261e8983f3347122c97e263da3 100644
--- a/firanka/series/series.py
+++ b/firanka/series/series.py
@@ -5,7 +5,7 @@ import six
 import functools
 import itertools
 
-from .range import Range
+from .range import Range, REAL_SET
 
 
 class Series(object):
@@ -38,9 +38,18 @@ class Series(object):
     def apply(self, series, fun):
         return AppliedSeries(self, series, fun)
 
-    def compute(self):
-        """Simplify self"""
-        return self
+    def translate(self, x):
+        return TranslatedSeries(self, x)
+
+
+class TranslatedSeries(Series):
+    def __init__(self, series, x):
+        super(TranslatedSeries, self).__init__(self.domain.translate(x))
+        self.series = series
+        self.x = x
+
+    def _get_for(self, item):
+        return self.series._get_for(item+self.x)
 
 
 class SlicedSeries(Series):
@@ -51,6 +60,7 @@ class SlicedSeries(Series):
     def _get_for(self, item):
         return self.parent._get_for(item)
 
+
 class DiscreteSeries(Series):
 
     def __init__(self, data, domain=None):
@@ -67,6 +77,75 @@ class DiscreteSeries(Series):
 
         raise RuntimeError('should never happen')
 
+    def translate(self, x):
+        return DiscreteSeries([(k+x, v) for k, v in self.data], self.domain.translate(x))
+
+    def apply(self, series, fun):
+        new_domain = self.domain.intersection(series.domain)
+
+        if isinstance(series, DiscreteSeries):
+            a = self.data[::-1]
+            b = series.data[::-1]
+
+            ptr = self.domain.start
+            c = [(ptr, fun(self._get_for(ptr), series._get_for(ptr)))]
+
+            def appendif(lst, ptr, v):
+                if len(lst) > 0:
+                    if lst[-1][0] >= ptr:
+                        return
+                lst.append((ptr, v))
+
+            while len(a) > 0 or len(b) > 0:
+                if len(a) > 0 and len(b) > 0:
+                    if a[-1] < b[-1]:
+                        ptr, v1 = a.pop()
+                        v2 = series._get_for(ptr)
+                    elif a[-1] > b[-1]:
+                        ptr, v1 = b.pop()
+                        v2 = self._get_for(ptr)
+                    else:
+                        ptr, v1 = a.pop()
+                        _, v2 = b.pop()
+
+                    assert ptr >= c[-1][0]
+
+                    appendif(c, ptr, fun(v1, v2))
+
+                else:
+                    if len(a) > 0:
+                        rest = a
+                        static_v = series._get_for(ptr)
+                        op = lambda me, const: fun(me, const)
+                    else:
+                        rest = b
+                        static_v = self._get_for(ptr)
+                        op = lambda me, const: fun(const, me)
+
+                    for ptr, v in rest:
+                        appendif(c, ptr, op(v, static_v))
+
+                    break
+        else:
+            if new_domain.start > self.data[0][0]:
+                c = [(new_domain.start, fun(self._get_for(new_domain.start), series._get_for(new_domain.start)))]
+            else:
+                c = []
+
+            for k, v in ((k, v) for k, v in self.data if new_domain.start <= k <= new_domain.stop):
+                newv = fun(v, series._get_for(k))
+
+                if len(c) > 0:
+                    if c[-1][1] == newv:
+                        continue
+
+                c.append((k, newv))
+
+            if c[-1][0] != new_domain.stop:
+                c.append((new_domain.stop, fun(self._get_for(new_domain.stop), series._get_for(new_domain.stop))))
+
+        return DiscreteSeries(c, new_domain)
+
     def compute(self):
         """Simplify self"""
         nd = [self.data[0]]
@@ -94,54 +173,20 @@ class AppliedSeries(Series):
     def _get_for(self, item):
         return self.op(self.ser1._get_for(item), self.ser2._get_for(item))
 
-    def compute(self):
-        """
-        Attempt to simplify the call tree
-        """
-        if isinstance(self.ser1, DiscreteSeries) and isinstance(self.ser2,
-                                                                DiscreteSeries):
-            a = [p for p, q in reversed(self.ser1.data)]
-            b = [p for p, q in reversed(self.ser2.data)]
+class ModuloSeries(Series):
+    def __init__(self, series):
+        super(ModuloSeries, self).__init__(REAL_SET)
 
-            ptr = self.domain.start
-            c = [(ptr, self._get_for(ptr))]
+        self.series = series
+        self.period = self.series.domain.length()
 
-            while len(a) > 0 or len(b) > 0:
-                if len(a) > 0 and len(b) > 0:
-                    if a[-1] < b[-1]:
-                        ptr = a.pop()
-                    elif a[-1] > b[-1]:
-                        ptr = b.pop()
-                    else:
-                        a.pop()
-                        ptr = b.pop()
-
-                    assert ptr >= c[-1][0]
-
-                    if ptr > c[-1][0]:
-                        c.append((ptr, self._get_for(ptr)))
-
-                else:
-                    rest = a if len(a) > 0 else b
-                    c.extend((ptr, self._get_for(ptr)) for ptr, v in rest)
-                    break
-
-            return DiscreteSeries(c, self.domain)
-        elif isinstance(self.ser1, DiscreteSeries) or isinstance(self.ser2,
-                                                                DiscreteSeries):
-            dis, nds = (self.ser1, self.ser2) if isinstance(self.ser1, DiscreteSeries) else (self.ser2, self.ser1)
-
-            if dis.data[0][0] != self.domain.start:
-                p = [(self.domain.start, self._get_for(self.domain.start))]
-            else:
-                p = []
-
-            for ptr, v in dis.data:
-                p.append((ptr, self._get_for(ptr)))
+    def _get_for(self, item):
+        if item < 0:
+            item = -(item // self.period) * self.period + item
+        elif item > self.period:
+            item = item - (item // self.period) * self.period
+        elif item == self.period:
+            item = 0
 
-            if dis.data[-1][0] != self.domain.stop:
-                dis.data.append((self.domain.stop, self._get_for(ptr)))
+        return self.series._get_for(self.series.domain.start + item)
 
-            return DiscreteSeries(p, self.domain)
-        else:
-            return self
diff --git a/tests/test_series/test_range.py b/tests/test_series/test_range.py
index 2bcf86757599255908c535bb87286047e63ff506..0a3c5b1d79270b9920ec2473d120479b3cf03c48 100644
--- a/tests/test_series/test_range.py
+++ b/tests/test_series/test_range.py
@@ -7,29 +7,26 @@ from firanka.series import Range
 class TestRange(unittest.TestCase):
     def do_intersect(self, a, b, val):
         if type(val) == bool:
-            if bool(Range(a).intersection(b)) != val:
-                self.fail('%s ^ %s != %s' % (Range(a), Range(b), val))
-            if bool(Range(b).intersection(a)) != val:
-                self.fail('%s ^ %s != %s' % (Range(b), Range(a), val))
+            p = Range(a).intersection(b)
+            if p.is_empty() != val:
+                self.fail('%s ^ %s [=> %s] != %s' % (Range(a), Range(b), p, val))
+            p = Range(b).intersection(a)
+            if p.is_empty() != val:
+                self.fail('%s ^ %s [=> %s] != %s' % (Range(b), Range(a), p, val))
         else:
             self.assertEqual(Range(a).intersection(b), Range(val))
             self.assertEqual(Range(b).intersection(a), Range(val))
 
     def test_isempty(self):
-        def tf(r, p):
-            s = Range(r)
-            self.assertEqual(s, r)
-            self.assertEqual(s.is_empty(), not p)
-
-        tf(Range(-1,-1,False,False), False)
-        tf(Range(-1,-1,False,True), True)
+        self.assertTrue(Range(-1,-1,False,False).is_empty())
+        self.assertFalse(Range(-1,-1,False,True).is_empty())
         self.assertEqual(Range(0,0,False,False), Range(2,2,False,False))
 
     def test_intersection(self):
-        self.do_intersect(Range(-10, -1, True, True), '<2;3>', False)
-        self.do_intersect(Range(-10, -1, True, False), '(-1;3>', False)
-        self.do_intersect('<-10;-1)', '<-1;1>', False)
-        self.do_intersect(Range(-10, -1, True, False), '(-1;3>', False)
+        self.do_intersect('<-10;1>', '<2;3>', True)
+        self.do_intersect('<10;1)', '(-1;3>', True)
+        self.do_intersect('<-10;-1)', '<-1;1>', True)
+        self.do_intersect('<-10;-1)', '(-1;3>', True)
         self.do_intersect('<-10;2)', '<1;5>', '<1;2)')
 
     def test_str_and_repr_and_bool(self):
diff --git a/tests/test_series/test_series.py b/tests/test_series/test_series.py
index 3ba6c1ecc2546783b7d8c2064e95301d101d8f4f..15a1517703857c7e7ad7ea206a26210210519c12 100644
--- a/tests/test_series/test_series.py
+++ b/tests/test_series/test_series.py
@@ -2,14 +2,12 @@
 from __future__ import print_function, absolute_import, division
 import six
 import unittest
-from firanka.series import DiscreteSeries, FunctionBasedSeries, Range
+from firanka.series import DiscreteSeries, FunctionBasedSeries, Range, ModuloSeries
 
 
 class TestDiscreteSeries(unittest.TestCase):
 
-
     def test_base(self):
-
         s = DiscreteSeries([[0,0], [1,1], [2,2]])
 
         self.assertEqual(s[0], 0)
@@ -19,7 +17,6 @@ class TestDiscreteSeries(unittest.TestCase):
         self.assertRaises(ValueError, lambda: s[-1])
         self.assertRaises(ValueError, lambda: s[2.5])
 
-
         s = DiscreteSeries([[0,0], [1,1], [2,2]], domain=Range(0,3,True,True))
         self.assertEqual(s[0], 0)
         self.assertEqual(s[0.5], 0)
@@ -28,6 +25,12 @@ class TestDiscreteSeries(unittest.TestCase):
         self.assertRaises(ValueError, lambda: s[-1])
         self.assertEqual(s[2.5], 2)
 
+    def test_translation(self):
+        s = DiscreteSeries([[0,0], [1,1], [2,2]]).translate(3)
+
+        self.assertEqual(s[3], 0)
+        self.assertEqual(s[3.5], 0)
+        self.assertEqual(s[4], 1)
 
     def test_slice(self):
         series = DiscreteSeries([[0, 0], [1, 1], [2, 2]])
@@ -46,22 +49,31 @@ class TestDiscreteSeries(unittest.TestCase):
         sb = DiscreteSeries([[0, 1], [1, 2], [2, 3]])
 
         sc = sa.apply(sb, lambda a, b: a+b)
-        sd = sc.compute()
+        self.assertIsInstance(sc, DiscreteSeries)
         self.assertEqual(sc.eval_points([0,1,2]), [1,3,5])
-        self.assertEqual(sd.eval_points([0,1,2]), sd.eval_points([0,1,2]))
-
-        self.assertEqual(sd.data, [(0,1),(1,3),(2,5)])
+        self.assertEqual(sc.data, [(0,1),(1,3),(2,5)])
 
     def test_eval2(self):
         sa = DiscreteSeries([[0, 0], [1, 1], [2, 2]])
         sb = FunctionBasedSeries(lambda x: x, '<0;2)')
 
         sc = sa.apply(sb, lambda a, b: a+b)
-        sd = sc.compute()
         self.assertEqual(sc.eval_points([0,1,2]), [0,2,4])
-        self.assertEqual(sd.eval_points([0,1,2]), sd.eval_points([0,1,2]))
 
-        self.assertEqual(sd.data, [(0,0),(1,2),(2,4)])
+        self.assertIsInstance(sc, DiscreteSeries)
+        self.assertEqual(sc.data, [(0,0),(1,2),(2,4)])
+
+    def test_eval3(self):
+        sa = FunctionBasedSeries(lambda x: x**2, '<-10;10)')
+        sb = FunctionBasedSeries(lambda x: x, '<0;2)')
+
+        sc = sa.apply(sb, lambda a, b: a*b)
+
+        PTS = [0,1,1.9]
+        EPTS = [x*x**2 for x in PTS]
+
+        self.assertEqual(sc.eval_points(PTS), EPTS)
+        self.assertTrue(Range('<0;2)') in sc.domain)
 
 
 class TestFunctionBasedSeries(unittest.TestCase):
@@ -76,3 +88,19 @@ class TestFunctionBasedSeries(unittest.TestCase):
         self.assertRaises(ValueError, lambda: sp[2])
         self.assertEqual(sp.domain.start, 0.5)
         self.assertEqual(sp.domain.stop, 1.5)
+
+class TestModuloSeries(unittest.TestCase):
+    def test_base(self):
+        series = ModuloSeries(DiscreteSeries([(0,1),(1,2),(2,3)], '<0;3)'))
+
+        self.assertEquals(series[3], 1)
+        self.assertEquals(series[4], 2)
+        self.assertEquals(series[5], 3)
+        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)')
+
+        ser3 = ser1.apply(ser2, lambda x, y: x*y)
+