Ticket #9556: trac_9556-dynamic_class_everywhere.patch

File trac_9556-dynamic_class_everywhere.patch, 7.5 KB (added by burcin, 6 years ago)
  • sage/libs/ginac.pxd

    # HG changeset patch
    # User Burcin Erocal <burcin@erocal.org>
    # Date 1371682857 -7200
    #      Thu Jun 20 01:00:57 2013 +0200
    # Node ID 3e84cf0290461d1888e074e7c65c5e9195f8ec8f
    # Parent  8dee7a1a3ce252db24e0c27567cb8d3ce96047cc
    trac 9556: add dynamic methods to symbolic expressions
    
    If a symbolic function represents the evaluated form of a symbolic function,
    it is useful in some cases to extract properties of this specific instance of
    the function. The methods used for this purpose should be defined close to the
    body of the symbolic function, but available as methods of symbolic
    expressions.
    
    This patch uses dynamic class magic to take methods defined in a subclass
    named EvaluationMethods in the function body and make them available as
    methods of symbolic expressions whenever the expression is the evaluated form
    of that function.
    
    This particular version of the implementation is built on ideas from Simon
    King, Mike Hansen and Volker Braun.
    
    One use case for this feature is #2516.
    
    diff --git a/sage/libs/ginac.pxd b/sage/libs/ginac.pxd
    a b  
    241241    bint is_a_power "is_a<power>" (GEx e)
    242242    bint is_a_fderivative "is_a<fderivative>" (GEx e)
    243243    bint is_a_function "is_a<function>" (GEx e)
     244    bint is_exactly_a_function "is_exactly_a<function>" (GEx e)
    244245    bint is_a_ncmul "is_a<ncmul>" (GEx e)
    245246
    246247    # Arithmetic
  • sage/symbolic/expression.pyx

    diff --git a/sage/symbolic/expression.pyx b/sage/symbolic/expression.pyx
    a b  
    149149from sage.rings.infinity import AnInfinity, infinity, minus_infinity, unsigned_infinity
    150150from sage.misc.decorators import rename_keyword
    151151from sage.misc.superseded import deprecated_function_alias
     152from sage.structure.dynamic_class import dynamic_class
    152153
    153154LOG_TEN_TWO_PLUS_EPSILON = 3.321928094887363 # a small overestimate of log(10,2)
    154155
     
    97869787    #    return new_Expression_from_GEx(self._parent, g_psi2(self._gobj, x._gobj))
    97879788
    97889789
     9790cdef dict dynamic_class_cache = {}
     9791cdef get_dynamic_class_for_function(unsigned serial):
     9792    r"""
     9793    Create a dynamic class corresponding to the function with given
     9794    ``serial`` that includes dynamic methods defined by the function.
     9795
     9796    Dynamic methods can be defined in a subclass ``EvaluationMethods`` in
     9797    the function body. These will be available in symbolic expressions
     9798    representing evaluations of the said function on some arguments.
     9799
     9800    EXAMPLES::
     9801
     9802        sage: from sage.symbolic.function import BuiltinFunction
     9803        sage: class TFunc(BuiltinFunction):
     9804        ....:     def __init__(self):
     9805        ....:         BuiltinFunction.__init__(self, 'tfunc', nargs=1)
     9806        ....:
     9807        ....:     class EvaluationMethods:
     9808        ....:         def argp1(self, x):
     9809        ....:             '''
     9810        ....:             Some documentation about a bogus function.
     9811        ....:             '''
     9812        ....:             return x+1
     9813        ....:
     9814        ....:         @property
     9815        ....:         def foo(self):
     9816        ....:             return 5
     9817        ....:
     9818        sage: tfunc = TFunc()
     9819        sage: e = tfunc(x); e
     9820        tfunc(x)
     9821        sage: type(e)
     9822        <class '__main__.Expression_with_dynamic_methods'>
     9823        sage: e.argp1()
     9824        x + 1
     9825        sage: e.foo
     9826        5
     9827        sage: x.argp1()
     9828        Traceback (most recent call last):
     9829        ...
     9830        AttributeError: 'sage.symbolic.expression.Expression' object has no
     9831        attribute 'argp1'
     9832        sage: t = (e + 1).op[0]; t
     9833        tfunc(x)
     9834        sage: t
     9835        tfunc(x)
     9836        sage: type(t)
     9837        <class '__main__.Expression_with_dynamic_methods'>
     9838        sage: t.argp1()
     9839        x + 1
     9840        sage: import sagenb.misc.support as s
     9841        sage: s.completions('t.argp', globals(), system='python')
     9842        ['t.argp1']
     9843        sage: t.argp1.__doc__.strip()
     9844        'Some documentation about a bogus function.'
     9845
     9846    Now with two arguments::
     9847
     9848        sage: class TFunc2(BuiltinFunction):
     9849        ....:     def __init__(self):
     9850        ....:         BuiltinFunction.__init__(self, 'tfunc', nargs=2)
     9851        ....:
     9852        ....:     class EvaluationMethods:
     9853        ....:         def argsum(self, x, y):
     9854        ....:             return x + y
     9855        ....:
     9856        sage: tfunc2 = TFunc2()
     9857        sage: e = tfunc2(x, 1)
     9858        sage: e.argsum()
     9859        x + 1
     9860    """
     9861    cls = dynamic_class_cache.get(serial)
     9862    if cls is None:
     9863        # if operator is a special function defined by us
     9864        # find the python equivalent and return it
     9865        func_class = get_sfunction_from_serial(serial)
     9866        eval_methods = getattr(func_class, 'EvaluationMethods', None)
     9867        if eval_methods is not None:
     9868            # callable methods need to be wrapped to extract the operands
     9869            # and pass them as arguments
     9870            from sage.symbolic.function_factory import eval_on_operands
     9871            from sage.structure.parent import getattr_from_other_class
     9872            for name in dir(eval_methods):
     9873                m = getattr_from_other_class(func_class, eval_methods, name)
     9874                if callable(m):
     9875                    setattr(eval_methods, name, eval_on_operands(m))
     9876            cls = dynamic_class('Expression_with_dynamic_methods',
     9877                    (eval_methods, Expression))
     9878        else:
     9879            cls = Expression
     9880
     9881        dynamic_class_cache[serial] = cls
     9882
     9883    return cls
    97899884
    97909885cdef Expression new_Expression_from_GEx(parent, GEx juice):
    97919886    cdef Expression nex
    9792     nex = <Expression>PY_NEW(Expression)
     9887    cdef unsigned serial
     9888    if is_exactly_a_function(juice):
     9889        # if the function defines any dynamic methods these are made
     9890        # available through a dynamic class
     9891        cls = get_dynamic_class_for_function(ex_to_function(juice).get_serial())
     9892    else:
     9893        cls = Expression
     9894
     9895    nex = <Expression>PY_NEW(cls)
    97939896    GEx_construct_ex(&nex._gobj, juice)
    97949897    nex._parent = parent
    97959898    return nex
  • sage/symbolic/function_factory.py

    diff --git a/sage/symbolic/function_factory.py b/sage/symbolic/function_factory.py
    a b  
    1313
    1414from sage.symbolic.function import SymbolicFunction, sfunctions_funcs, \
    1515        unpickle_wrapper
     16from sage.misc.decorators import sage_wraps
    1617
    1718def function_factory(name, nargs=0, latex_name=None, conversions=None,
    1819            evalf_params_first=True, eval_func=None, evalf_func=None,
     
    349350        return func(*args, prec=prec)
    350351    return new_evalf
    351352
     353
     354# This code is used when constructing dynamic methods for symbolic
     355# expressions representing evaluated symbolic functions. See
     356# get_dynamic_class_for_function in sage/symbolic/expression.pyx.
     357# Since Cython does not support closures, this needs to live in a Python
     358# file. This file is the only pure Python file we have related to symbolic
     359# functions.
     360def eval_on_operands(f):
     361    """
     362    Given a method ``f`` return a new method which takes a single symbolic
     363    expression argument and passes the operands of the given expression as
     364    arguments to ``f``.
     365
     366    EXAMPLES::
     367
     368        sage: def f(x, y):
     369        ....:     '''
     370        ....:     Some documentation.
     371        ....:     '''
     372        ....:     return x + 2*y
     373        ....:
     374        sage: f(x, 1)
     375        x + 2
     376        sage: from sage.symbolic.function_factory import eval_on_operands
     377        sage: g = eval_on_operands(f)
     378        sage: g(x + 1)
     379        x + 2
     380        sage: g.__doc__.strip()
     381        'Some documentation.'
     382    """
     383    @sage_wraps(f)
     384    def new_f(ex):
     385        return f(*ex.operands())
     386    return new_f