Ticket #9989: trac_9989-operands.take3.patch

File trac_9989-operands.take3.patch, 12.2 KB (added by burcin, 11 years ago)
  • module_list.py

    # HG changeset patch
    # User Burcin Erocal <burcin@erocal.org>
    # Date 1306854546 -7200
    # Node ID a6ea5c40ff15333a5cdbfe23630c4d34f6a5b610
    # Parent  4b8c613aa9efa5bd6999bcba686ead7ea47a190e
    trac 9989: allow easier access to parts of a symbolic expression.
    
    diff --git a/module_list.py b/module_list.py
    a b  
    16631663              depends = ginac_depends,
    16641664              libraries = ["pynac", "gmp"]),
    16651665   
     1666    Extension('sage.symbolic.getitem',
     1667              sources = ['sage/symbolic/getitem.pyx'],
     1668              language = 'c++',
     1669              depends = [SAGE_ROOT + "/local/include/pynac/ginac.h"],
     1670              libraries = ["pynac", "gmp"]),
     1671   
    16661672    Extension('sage.symbolic.function',
    16671673              sources = ['sage/symbolic/function.pyx'],
    16681674              language = 'c++',
  • sage/symbolic/expression.pyx

    diff --git a/sage/symbolic/expression.pyx b/sage/symbolic/expression.pyx
    a b  
    141141import sage.rings.integer
    142142import sage.rings.rational
    143143from sage.structure.element cimport ModuleElement, RingElement, Element
     144from sage.symbolic.getitem cimport OperandsWrapper
    144145from sage.symbolic.function import get_sfunction_from_serial, SymbolicFunction
    145146from sage.rings.rational import Rational  # Used for sqrt.
    146147from sage.misc.derivative import multi_derivative
     
    25062507            2*4^(1/4)
    25072508        """
    25082509        cdef Expression base, nexp
    2509        
     2510
    25102511        try:
    25112512            # self is an Expression and exp might not be
    25122513            base = self
     
    38113812            sage: a[:SR(5)]
    38123813            [0, 1, 2, 3, 4]
    38133814        """
    3814         return int(self._integer_())   
     3815        return int(self._integer_())
    38153816
    38163817    def iterator(self):
    38173818        """
     
    38483849                    raise ValueError, "expressions containing only a numeric coefficient, constant or symbol have no operands"
    38493850        return new_ExpIter_from_Expression(self)
    38503851
    3851 ##     def __getitem__(self, ind):
    3852 ##         """
    3853 ##         EXAMPLES::
    3854        
    3855 ##             sage: x,y,z = var('x,y,z')
    3856 ##             sage: e = x + x*y + z^y + 3*y*z; e
    3857 ##             x*y + 3*y*z + z^y + x
    3858 ##             sage: e[1]
    3859 ##             3*y*z
    3860 ##             sage: e[-1]
    3861 ##             x
    3862 ##             sage: e[1:]
    3863 ##             [3*y*z, z^y, x]
    3864 ##             sage: e[:2]
    3865 ##             [x*y, 3*y*z]
    3866 ##             sage: e[-2:]
    3867 ##             [z^y, x]
    3868 ##             sage: e[:-2]
    3869 ##             [x*y, 3*y*z]
    3870 
    3871 ##         """
    3872 ##         cdef int bind, eind, step, i
    3873 ##         cdef int n_ops = self._gobj.nops()
    3874 ##         if PY_TYPE_CHECK(ind, slice):
    3875 ##             if ind.start:
    3876 ##                 bind = ind.start
    3877 ##                 if bind != ind.start:
    3878 ##                     raise ValueError, "integer index expected"
    3879 ##                 if bind < 0:
    3880 ##                     bind = n_ops + bind
    3881 ##             else:
    3882 ##                 bind = 0
    3883 ##             if ind.stop:
    3884 ##                 eind = ind.stop
    3885 ##                 if eind != ind.stop:
    3886 ##                     raise ValueError, "integer index expected"
    3887 ##                 if eind > n_ops:
    3888 ##                     eind = n_ops
    3889 ##                 if eind < 0:
    3890 ##                     eind = n_ops + eind
    3891 ##             else:
    3892 ##                 eind = n_ops
    3893 ##             if ind.step:
    3894 ##                 step = ind.step
    3895 ##                 if step != ind.step:
    3896 ##                     raise ValueError, "step value must be an integer"
    3897 ##             else:
    3898 ##                 step = 1
    3899 ##             return [new_Expression_from_GEx(self._parent, self._gobj.op(i))
    3900 ##                     for i in xrange(bind, eind, step)]
    3901 
    3902 ##         try:
    3903 ##             bind = ind
    3904 ##             if bind != ind:
    3905 ##                 raise ValueError, "integer index expected"
    3906 ##         except TypeError:
    3907 ##             raise TypeError, "index should either be a slice object, or an integer"
    3908 ##         if bind < 0:
    3909 ##             bind = n_ops + bind
    3910 ##         return new_Expression_from_GEx(self._parent, self._gobj.op(bind))
     3852    property op:
     3853        def __get__(self):
     3854            """
     3855            Provide access to the operands of an expression through a property.
     3856
     3857
     3858            EXAMPLES::
     3859
     3860                sage: t = 1+x+x^2
     3861                sage: t.op
     3862                Operands wrapper for expression x^2 + x + 1
     3863                sage: x.op
     3864                Traceback (most recent call last):
     3865                ...
     3866                TypeError: expressions containing only a numeric coefficient, constant or symbol have no operands
     3867                sage: t.op[0]
     3868                x^2
     3869
     3870            Indexing directly with ``t[1]`` causes problems with numpy types.
     3871
     3872                sage: t[1]
     3873                Traceback (most recent call last):
     3874                ...
     3875                TypeError: 'sage.symbolic.expression.Expression' object does not support indexing
     3876            """
     3877            if is_a_symbol(self._gobj) or is_a_constant(self._gobj) or \
     3878                is_a_numeric(self._gobj):
     3879                    raise TypeError, "expressions containing only a numeric coefficient, constant or symbol have no operands"
     3880            cdef OperandsWrapper res = OperandsWrapper.__new__(OperandsWrapper)
     3881            res._expr = self
     3882            return res
    39113883
    39123884    def _numerical_approx(self, prec=None, digits=None):
    39133885        """
  • new file sage/symbolic/getitem.pxd

    diff --git a/sage/symbolic/getitem.pxd b/sage/symbolic/getitem.pxd
    new file mode 100644
    - +  
     1include "../libs/ginac/decl.pxi"
     2from sage.symbolic.expression cimport Expression
     3from sage.structure.sage_object cimport SageObject
     4
     5cdef class OperandsWrapper(SageObject):
     6    cdef Expression _expr
  • new file sage/symbolic/getitem.pyx

    diff --git a/sage/symbolic/getitem.pyx b/sage/symbolic/getitem.pyx
    new file mode 100644
    - +  
     1###############################################################################
     2#   Sage: Open Source Mathematical Software
     3#       Copyright (C) 2011 Burcin Erocal <burcin@erocal.org>
     4#  Distributed under the terms of the GNU General Public License (GPL),
     5#  version 2 or any later version.  The full text of the GPL is available at:
     6#                  http://www.gnu.org/licenses/
     7###############################################################################
     8from sage.symbolic.expression cimport new_Expression_from_GEx
     9
     10cdef inline int normalize_index(object arg, int nops, object err_msg) except -1:
     11    """
     12    Given an index ``arg`` and the number of operands ``nops`` return
     13    an integer between 0 and ``nops`` which will be used as the index to fetch
     14    the data from the underlying vector.
     15
     16    TESTS::
     17
     18        sage: from sage.symbolic.getitem import normalize_index_for_doctests as normalize_index
     19        sage: normalize_index(-1, 4)
     20        3
     21        sage: normalize_index(1, 4)
     22        1
     23        sage: normalize_index(1.5, 4)
     24        Traceback (most recent call last):
     25        ...
     26        TypeError: some error message
     27        sage: normalize_index(-5, 4)
     28        Traceback (most recent call last):
     29        ...
     30        IndexError: operand index out of range, got -5, expect between -4 and 3
     31        sage: normalize_index(5, 4)
     32        Traceback (most recent call last):
     33        ...
     34        IndexError: operand index out of range, got 5, expect between -4 and 3
     35    """
     36    cdef int i
     37    i = arg
     38    if i != arg:
     39        raise TypeError(err_msg)
     40    if i < 0:
     41        i = nops + i
     42    if i >= nops or i < 0:
     43        raise IndexError("operand index out of range, got %s, expect between %s and %s"%(arg,-nops,nops-1))
     44    return i
     45
     46def normalize_index_for_doctests(arg, nops):
     47    """
     48    Wrapper function to test ``normalize_index``.
     49
     50    TESTS::
     51
     52        sage: from sage.symbolic.getitem import normalize_index_for_doctests
     53        sage: normalize_index_for_doctests(-1, 4)
     54        3
     55    """
     56    return normalize_index(arg, nops, "some error message")
     57
     58cdef class OperandsWrapper(SageObject):
     59    """
     60    Operands wrapper for symbolic expressions.
     61
     62    EXAMPLES::
     63
     64        sage: x,y,z = var('x,y,z')
     65        sage: e = x + x*y + z^y + 3*y*z; e
     66        x*y + 3*y*z + z^y + x
     67        sage: e.op[1]
     68        3*y*z
     69        sage: e.op[1,1]
     70        z
     71        sage: e.op[-1]
     72        x
     73        sage: e.op[1:]
     74        [3*y*z, z^y, x]
     75        sage: e.op[:2]
     76        [x*y, 3*y*z]
     77        sage: e.op[-2:]
     78        [z^y, x]
     79        sage: e.op[:-2]
     80        [x*y, 3*y*z]
     81        sage: e.op[-5]
     82        Traceback (most recent call last):
     83        ...
     84        IndexError: operand index out of range, got -5, expect between -4 and 3
     85        sage: e.op[5]
     86        Traceback (most recent call last):
     87        ...
     88        IndexError: operand index out of range, got 5, expect between -4 and 3
     89        sage: e.op[1,1,0]
     90        Traceback (most recent call last):
     91        ...
     92        TypeError: expressions containing only a numeric coefficient, constant or symbol have no operands
     93        sage: e.op[:1.5]
     94        Traceback (most recent call last):
     95        ...
     96        TypeError: slice indices must be integers or None or have an __index__ method
     97        sage: e.op[:2:1.5]
     98        Traceback (most recent call last):
     99        ...
     100        ValueError: step value must be an integer
     101    """
     102    def __getitem__(self, arg):
     103        """
     104        TESTS::
     105
     106           sage: t = 1+x+x^2
     107           sage: t.op[1:]
     108           [x, 1]
     109        """
     110        cdef int ind, nops
     111        cdef int bind, eind, step
     112        cdef GEx cur_ex
     113        if isinstance(arg, slice):
     114            nops = self._expr._gobj.nops()
     115
     116            slice_err_msg = "slice indices must be integers or None or have an __index__ method"
     117            if arg.start:
     118                bind = normalize_index(arg.start, nops, slice_err_msg)
     119            else:
     120                bind = 0
     121            if arg.stop:
     122                eind = normalize_index(arg.stop, nops, slice_err_msg)
     123            else:
     124                eind = nops
     125            if arg.step:
     126                step = arg.step
     127                if step != arg.step:
     128                    raise ValueError, "step value must be an integer"
     129            else:
     130                step = 1
     131            return [new_Expression_from_GEx(self._expr._parent,
     132                self._expr._gobj.op(ind)) for ind in xrange(bind, eind, step)]
     133
     134
     135        ind_err_msg = "index should either be a slice object, an integer or a list of integers"
     136        if isinstance(arg, (list, tuple)):
     137            # handle nested index
     138            if len(arg) == 0: # or not all(lambda x: x in ZZ for t in args):
     139                raise TypeError, ind_err_msg
     140            GEx_construct_ex(&cur_ex, self._expr._gobj)
     141            for x in arg:
     142                nops = cur_ex.nops()
     143                if nops == 0:
     144                    raise TypeError, "expressions containing only a numeric coefficient, constant or symbol have no operands"
     145                ind = normalize_index(x, nops, ind_err_msg)
     146                cur_ex = cur_ex.op(ind)
     147            return new_Expression_from_GEx(self._expr._parent, cur_ex)
     148
     149        ind = normalize_index(arg, self._expr._gobj.nops(), ind_err_msg)
     150        return new_Expression_from_GEx(self._expr._parent,
     151                self._expr._gobj.op(ind))
     152
     153    def _repr_(self):
     154        """
     155        TESTS::
     156
     157            sage: (x^2).op
     158            Operands wrapper for expression x^2
     159        """
     160        return "Operands wrapper for expression %s"%(self._expr)
     161
     162    def _latex_(self):
     163        r"""
     164        TESTS::
     165
     166            sage: latex((x^2).op)
     167            \text{Operands wrapper for expression }x^{2}
     168        """
     169        return r"\text{Operands wrapper for expression }%s"%(self._expr._latex_())
     170
     171    def __reduce__(self):
     172        """
     173        TESTS::
     174
     175            sage: (x^2).op.__reduce__()
     176            (<built-in function restore_op_wrapper>, (x^2,))
     177            sage: loads(dumps((x^2).op))
     178            Operands wrapper for expression x^2
     179        """
     180        return restore_op_wrapper, (self._expr,)
     181
     182def restore_op_wrapper(expr):
     183    """
     184    TESTS::
     185
     186        sage: from sage.symbolic.getitem import restore_op_wrapper
     187        sage: restore_op_wrapper(x^2)
     188        Operands wrapper for expression x^2
     189    """
     190    return expr.op