From: Michael Orlitzky Date: Sun, 4 Nov 2012 21:12:49 +0000 (-0500) Subject: Add the SymbolSequence class; eventually headed upstream. X-Git-Url: http://gitweb.michael.orlitzky.com/?a=commitdiff_plain;h=64c3510634164b0d8a43c2f034bdcc0c24b281cb;p=sage.d.git Add the SymbolSequence class; eventually headed upstream. --- diff --git a/mjo/all.py b/mjo/all.py index 4169cee..83ecf45 100644 --- a/mjo/all.py +++ b/mjo/all.py @@ -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 index 0000000..4384427 --- /dev/null +++ b/mjo/symbol_sequence.py @@ -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)