]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
Add the SymbolSequence class; eventually headed upstream.
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 4 Nov 2012 21:12:49 +0000 (16:12 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Sun, 4 Nov 2012 21:12:49 +0000 (16:12 -0500)
mjo/all.py
mjo/symbol_sequence.py [new file with mode: 0644]

index 4169cee30a4b55b3692451f8e644ddee3e669a04..83ecf4559dd628a53db9c2253fa16e14d325a99d 100644 (file)
@@ -6,4 +6,5 @@ in his script. Instead, he can just `from mjo.all import *`.
 from interpolation import *
 from misc import *
 from plot import *
+from symbol_sequence import *
 from symbolic import *
diff --git a/mjo/symbol_sequence.py b/mjo/symbol_sequence.py
new file mode 100644 (file)
index 0000000..4384427
--- /dev/null
@@ -0,0 +1,219 @@
+from sage.all import *
+
+class SymbolSequence:
+    """
+    A callable object which imitates a function from ZZ^n to a
+    sequence with n subscripts.
+
+    INPUT:
+
+      - ``name`` -- The sequence name.
+
+      - ``latex_name`` -- An optional latex expression (string) to
+        use instead of `name` when converting the symbols to latex.
+
+      - ``domain`` -- A string representing the domain of the symbol,
+        either 'real', 'complex', or 'positive'.
+
+    OUTPUT:
+
+    A callable object returning symbolic expressions.
+
+    EXAMPLES:
+
+    Create coefficients for polynomials of arbitrary degree::
+
+        sage: a = SymbolSequence('a')
+        sage: p = sum([ a(i)*x^i for i in range(0,5)])
+        sage: p
+        a4*x^4 + a3*x^3 + a2*x^2 + a1*x + a0
+
+    Using a different latex name since 'lambda' is reserved::
+
+        sage: l = SymbolSequence('l', '\lambda')
+        sage: l(0)
+        l0
+        sage: latex(l(0))
+        \lambda_{0}
+
+    Using multiple indices::
+
+        sage: a = SymbolSequence('a')
+        sage: a(0,1,2)
+        a012
+        sage: latex(a(0,1,2))
+        a_{0}_{1}_{2}
+        sage: [ a(i,j) for i in range(0,2) for j in range(0,2) ]
+        [a00, a01, a10, a11]
+
+    If no index is given, an unsubscripted symbol is returned::
+
+        sage: a = SymbolSequence('a')
+        sage: a()
+        a
+
+    You can pass slice objects instead of integers to obtain a list of
+    symbols::
+
+        sage: a = SymbolSequence('a')
+        sage: a(slice(5,7))
+        [a5, a6]
+
+    This even works for the second, third, etc. indices::
+
+        sage: a = SymbolSequence('a')
+        sage: a(slice(0,2), slice(0,2))
+        [a00, a01, a10, a11]
+
+    You can also index with the list index operator::
+
+        sage: a = SymbolSequence('a')
+        sage: a[1]
+        a1
+
+    This allows you to retrieve one-dimensional slices easily::
+
+        sage: a = SymbolSequence('a')
+        sage: a[0:5]
+        [a0, a1, a2, a3, a4]
+
+    TESTS:
+
+    We shouldn't overwrite variables in the global namespace::
+
+        sage: a = SymbolSequence('a')
+        sage: a0 = 4
+        sage: a(0)
+        a0
+        sage: a0
+        4
+
+    The symbol at a given index should always be the same, even when
+    the symbols themselves are unnamed. We store the string
+    representation and compare because the output is unpredictable::
+
+        sage: a = SymbolSequence()
+        sage: a0str = str(a(0))
+        sage: str(a(0)) == a0str
+        True
+
+    Slices and single indices work when combined::
+
+        sage: a = SymbolSequence('a')
+        sage: a(3, slice(0,2))
+        [a30, a31]
+        sage: a(slice(0,2), 3)
+        [a03, a13]
+
+    """
+
+    def __init__(self, name=None, latex_name=None, domain=None):
+        # We store a dict of already-created symbols so that we don't
+        # recreate a symbol which already exists. This is especially
+        # helpful when using unnamed variables, if you want e.g. a(0)
+        # to return the same variable each time.
+        #
+        # The entry corresponding to None is the un-subscripted symbol
+        # with our name.
+        unsubscripted = SR.symbol(name, latex_name, domain)
+        self._symbols = { None: unsubscripted }
+
+        self._name = name
+        self._latex_name = latex_name
+        self._domain = domain
+
+
+    def _create_symbol_(self, subscript):
+        if self._name is None:
+            # Allow creating unnamed symbols, for consistency with
+            # SR.symbol().
+            name = None
+        else:
+            name = '%s%d' % (self._name, subscript)
+
+        if self._latex_name is None:
+            latex_name = None
+        else:
+            latex_name = r'%s_{%d}' % (self._latex_name, subscript)
+
+        return SR.symbol(name, latex_name, self._domain)
+
+
+    def _flatten_list_(self, l):
+        """
+        Recursively flatten the given list, allowing for some elements
+        to be non-iterable.
+        """
+        result = []
+
+        for item in l:
+            if isinstance(item, list):
+                result += self._flatten_list_(item)
+            else:
+                result += [item]
+
+        return result
+
+
+    def __getitem__(self, key):
+        if isinstance(key, slice):
+            # We were given a slice. Clean up some of its properties
+            # first. The start/step are default for lists. We make
+            # copies of these because they're read-only.
+            (start, step) = (key.start, key.step)
+            if start is None:
+                start = 0
+            if key.stop is None:
+                # Would otherwise loop forever since our "length" is
+                # undefined.
+                raise ValueError('You must supply an terminal index')
+            if step is None:
+               step = 1
+
+            # If the user asks for a slice, we'll be returning a list
+            # of symbols.
+            return [ self(idx) for idx in range(start, key.stop, step) ]
+        else:
+            return self(key)
+
+
+    def __call__(self, *args):
+        args = list(args)
+
+        if len(args) == 0:
+            return self._symbols[None]
+
+        # This is safe after the len == 0 test.
+        key = args[0]
+        args = args[1:] # Peel off the first arg, which we've called 'key'
+
+        if isinstance(key, slice):
+            if len(args) == 0:
+                return self[key]
+            else:
+                v = self[key]
+                ss = [ SymbolSequence(w._repr_(), w._latex_(), self._domain)
+                       for w in v ]
+
+                # This might be nested...
+                maybe_nested_list = [ s(*args) for s in ss ]
+                return self._flatten_list_(maybe_nested_list)
+
+        if key < 0:
+            # Cowardly refuse to create a variable named "a-1".
+            raise IndexError('Indices must be nonnegative')
+
+        if len(args) == 0:
+            # Base case, create a symbol and return it.
+            try:
+                return self._symbols[key]
+            except KeyError:
+                self._symbols[key] = self._create_symbol_(key)
+                return self._symbols[key]
+        else:
+            # If we're given more than one index, we want to create the
+            # subsequences recursively. For example, if we're asked for
+            # x(1,2), this is really SymbolSequence('x1')(2).
+            v = self(key) # x(1) -> x1
+            ss = SymbolSequence(v._repr_(), v._latex_(), self._domain)
+            return ss(*args)