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)