Ticket #11576: sage-trac_11576.patch

File sage-trac_11576.patch, 10.9 KB (added by mjo, 6 years ago)

Add tests for the individual class methods

  • sage/symbolic/ring.pyx

    # HG changeset patch
    # User Michael Orlitzky <michael@orlitzky.com>
    # Date 1354381212 18000
    # Node ID ddfab2ba7a2ee5b3d270487189c91fc6bd6e5649
    # Parent  23061edf00176562a9d754123cfebbb8e3f631d3
    Trac #11576: Implement sequences of symbols.
    
    * Add a new class, SymbolSequence to symbolic/ring.pyx. This class is
      iterable, and handles the generation of the symbols.
    
      Its constructor takes three arguments; they each map to one of the
      arguments of the SymbolicRing.symbol() function. When creating a new
      symbol, these values will be passed to SymbolicRing.symbol().
    
    * Add a new method, symbols(), to the SymbolicRing class. This method
      simply returns an instance of the SymbolSequence class.
    
    diff --git a/sage/symbolic/ring.pyx b/sage/symbolic/ring.pyx
    a b  
    517517            GEx_construct_symbol(&e._gobj, symb)
    518518        return e
    519519
     520
     521    def symbols(self, name=None, latex_name=None, domain=None):
     522        """
     523        Construct an iterable object which acts like a sequence of
     524        symbolic expressions (variables).
     525
     526        INPUT:
     527
     528        - ``name`` -- The sequence name. For example, if you name the
     529          sequence `x`, the variables will be called `x0`, `x1`,...
     530
     531        - ``latex_name`` -- An optional latex expression (string) to
     532          use instead of `name` when converting the symbols to latex.
     533
     534        - ``domain`` -- A string representing the domain of the symbol,
     535          either 'real', 'complex', or 'positive'.
     536
     537        OUTPUT:
     538
     539        An iterable object containing symbolic expressions.
     540
     541        EXAMPLES:
     542
     543        The simplest use case::
     544
     545            sage: a = SR.symbols('a')
     546            sage: a[0]
     547            a0
     548            sage: a[1]
     549            a1
     550
     551        Create polynomials with symbolic coefficients of arbitrary
     552        degree::
     553
     554            sage: a = SR.symbols('a')
     555            sage: p = sum([ a[i]*x^i for i in range(0,5)])
     556            sage: p
     557            a4*x^4 + a3*x^3 + a2*x^2 + a1*x + a0
     558
     559        Using a different latex name since 'lambda' is reserved::
     560
     561            sage: l = SR.symbols('l', '\lambda')
     562            sage: l[0]
     563            l0
     564            sage: latex(l[0])
     565            \lambda_{0}
     566
     567        Using multiple indices::
     568
     569            sage: a = SR.symbols('a')
     570            sage: a[0,1,2]
     571            a012
     572            sage: latex(a[0,1,2])
     573            a_{0}_{1}_{2}
     574            sage: [ a[i,j] for i in range(0,2) for j in range(0,2) ]
     575            [a00, a01, a10, a11]
     576
     577        You can pass slices instead of integers to obtain a list of
     578        symbols::
     579
     580            sage: a = SR.symbols('a')
     581            sage: a[5:7]
     582            [a5, a6]
     583
     584        This even works for the second, third, etc. indices::
     585
     586            sage: a = SR.symbols('a')
     587            sage: a[0:2, 0:2]
     588            [a00, a01, a10, a11]
     589
     590        TESTS:
     591
     592        We shouldn't overwrite variables in the global namespace::
     593
     594            sage: a = SR.symbols('a')
     595            sage: a0 = 4
     596            sage: a[0]
     597            a0
     598            sage: a0
     599            4
     600
     601        The symbol at a given index should always be the same, even
     602        when the symbols themselves are unnamed. We store the string
     603        representation and compare because the output is unpredictable::
     604
     605            sage: a = SR.symbols()
     606            sage: a0str = str(a[0])
     607            sage: str(a[0]) == a0str
     608            True
     609
     610        Slices and single indices work when combined::
     611
     612            sage: a = SR.symbols('a')
     613            sage: a[3, 0:2]
     614            [a30, a31]
     615            sage: a[0:2, 3]
     616            [a03, a13]
     617
     618        """
     619        return SymbolSequence(name, latex_name, domain)
     620
     621
    520622    cpdef var(self, name, latex_name=None, domain=None):
    521623        """
    522624        Return the symbolic variable defined by x as an element of the
     
    894996    return len(code.co_names)==1 and code.co_names[0]==x
    895997   
    896998   
     999
     1000
     1001class SymbolSequence:
     1002    """
     1003    An iterable object which acts like a sequence of symbolic
     1004    expressions (variables). The full documentation and test suite can
     1005    be found under SymbolicRing.symbols().
     1006    """
     1007
     1008    def __init__(self, name=None, latex_name=None, domain=None):
     1009        # We store a dict of already-created symbols so that we don't
     1010        # recreate a symbol which already exists. This is especially
     1011        # helpful when using unnamed variables, if you want e.g. a[0]
     1012        # to return the same variable each time.
     1013        self._symbols = {}
     1014
     1015        self._name = name
     1016        self._latex_name = latex_name
     1017        self._domain = domain
     1018
     1019
     1020    def _create_symbol_(self, subscript):
     1021        """
     1022        Return a symbol with the given subscript. Creates the
     1023        appropriate name and latex_name before delegating to
     1024        SR.symbol().
     1025
     1026        EXAMPLES::
     1027
     1028            sage: from sage.symbolic.ring import SymbolSequence
     1029            sage: a = SymbolSequence('a', 'alpha', 'real')
     1030            sage: a1 = a._create_symbol_(1)
     1031            sage: a1
     1032            a1
     1033            sage: latex(a1)
     1034            alpha_{1}
     1035
     1036        """
     1037        # Allow creating unnamed symbols, for consistency with
     1038        # SR.symbol().
     1039        name = None
     1040        if self._name is not None:
     1041            name = '%s%d' % (self._name, subscript)
     1042
     1043        latex_name = None
     1044        if self._latex_name is not None:
     1045            latex_name = r'%s_{%d}' % (self._latex_name, subscript)
     1046
     1047        return SR.symbol(name, latex_name, self._domain)
     1048
     1049
     1050    def _flatten_list_(self, l):
     1051        """
     1052        Recursively flatten the given list, allowing for some elements
     1053        to be non-iterable. This is slow, but also works, which is
     1054        more than can be said about some of the snappier solutions of
     1055        lore.
     1056
     1057        EXAMPLES::
     1058
     1059            sage: from sage.symbolic.ring import SymbolSequence
     1060            sage: a = SymbolSequence('a')
     1061            sage: a._flatten_list_([1,2,3])
     1062            [1, 2, 3]
     1063            sage: a._flatten_list_([1,[2,3]])
     1064            [1, 2, 3]
     1065            sage: a._flatten_list_([1,[2,[3]]])
     1066            [1, 2, 3]
     1067            sage: a._flatten_list_([[[[[1,[2,[3]]]]]]])
     1068            [1, 2, 3]
     1069
     1070        """
     1071        result = []
     1072
     1073        for item in l:
     1074            if isinstance(item, list):
     1075                result += self._flatten_list_(item)
     1076            else:
     1077                result += [item]
     1078
     1079        return result
     1080
     1081
     1082    def __getitem__(self, key):
     1083        """
     1084        This handles individual integer arguments, slices, and
     1085        tuples. It just hands off the real work to
     1086        self._subscript_foo_().
     1087
     1088        EXAMPLES:
     1089
     1090        An integer argument::
     1091
     1092            sage: from sage.symbolic.ring import SymbolSequence
     1093            sage: a = SymbolSequence('a')
     1094            sage: a.__getitem__(1)
     1095            a1
     1096
     1097        A tuple argument::
     1098
     1099            sage: from sage.symbolic.ring import SymbolSequence
     1100            sage: a = SymbolSequence('a')
     1101            sage: a.__getitem__((1,2))
     1102            a12
     1103
     1104        A slice argument::
     1105
     1106            sage: from sage.symbolic.ring import SymbolSequence
     1107            sage: a = SymbolSequence('a')
     1108            sage: a.__getitem__(slice(1,4))
     1109            [a1, a2, a3]
     1110
     1111        """
     1112        if isinstance(key, tuple):
     1113            return self._subscript_tuple_(key)
     1114
     1115        if isinstance(key, slice):
     1116            return self._subscript_slice_(key)
     1117
     1118        # This is the most common case so it would make sense to have
     1119        # this test first. But there are too many different "integer"
     1120        # classes that we'd have to check for.
     1121        return self._subscript_integer_(key)
     1122
     1123
     1124    def _subscript_integer_(self, n):
     1125        """
     1126        The subscript is a single integer, or something that acts like
     1127        one.
     1128
     1129        EXAMPLES::
     1130
     1131            sage: from sage.symbolic.ring import SymbolSequence
     1132            sage: a = SymbolSequence('a')
     1133            sage: a._subscript_integer_(123)
     1134            a123
     1135
     1136        """
     1137        if n < 0:
     1138            # Cowardly refuse to create a variable named "a-1".
     1139            raise IndexError('Indices must be nonnegative')
     1140
     1141        try:
     1142            return self._symbols[n]
     1143        except KeyError:
     1144            self._symbols[n] = self._create_symbol_(n)
     1145            return self._symbols[n]
     1146
     1147
     1148    def _subscript_slice_(self, s):
     1149        """
     1150        We were given a slice. Clean up some of its properties
     1151        first. The start/step are default for lists. We make
     1152        copies of these because they're read-only.
     1153
     1154        EXAMPLES::
     1155
     1156            sage: from sage.symbolic.ring import SymbolSequence
     1157            sage: a = SymbolSequence('a')
     1158            sage: a._subscript_slice_(slice(1,3))
     1159            [a1, a2]
     1160
     1161        """
     1162        (start, step) = (s.start, s.step)
     1163        if start is None:
     1164            start = 0
     1165        if s.stop is None:
     1166            # Would otherwise loop forever since our "length" is
     1167            # undefined.
     1168            raise ValueError('You must supply an terminal index')
     1169        if step is None:
     1170            step = 1
     1171
     1172        # If the user asks for a slice, we'll be returning a list
     1173        # of symbols.
     1174        return [ self._subscript_integer_(idx)
     1175                 for idx in range(start, s.stop, step) ]
     1176
     1177
     1178
     1179    def _subscript_tuple_(self, args):
     1180        """
     1181        When we have more than one level of subscripts, we pick off
     1182        the first one and generate the rest recursively.
     1183
     1184        EXAMPLES:
     1185
     1186        A simple two-tuple::
     1187
     1188            sage: from sage.symbolic.ring import SymbolSequence
     1189            sage: a = SymbolSequence('a')
     1190            sage: a._subscript_tuple_((1,8))
     1191            a18
     1192
     1193        Nested tuples::
     1194
     1195            sage: a._subscript_tuple_(( (1,2), (3,(4,5,6)) ))
     1196            a123456
     1197
     1198        """
     1199
     1200        # We never call this method without an argument.
     1201        key = args[0]
     1202        args = args[1:] # Peel off the first arg, which we've called 'key'
     1203
     1204        # We don't know the type of 'key', but __getitem__ will figure
     1205        # it out and dispatch properly.
     1206        v = self[key]
     1207        if len(args) == 0:
     1208            # There was only one element left in the tuple.
     1209            return v
     1210
     1211        # At this point, we know we were given at least a two-tuple.
     1212        # The symbols corresponding to the first entry are already
     1213        # computed, in 'v'. Here we recursively compute the symbols
     1214        # corresponding to the second coordinate, with the first
     1215        # coordinate(s) fixed.
     1216        if isinstance(key, slice):
     1217            ss = [ SymbolSequence(w._repr_(), w._latex_(), self._domain)
     1218                   for w in v ]
     1219
     1220            # This might be nested...
     1221            maybe_nested_list = [ s._subscript_tuple_(args) for s in ss ]
     1222            return self._flatten_list_(maybe_nested_list)
     1223
     1224        else:
     1225            # If it's not a slice, it's an integer.
     1226            ss = SymbolSequence(v._repr_(), v._latex_(), self._domain)
     1227            return ss._subscript_tuple_(args)