Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

How to create a symbolic function returning a function ?

[ Context: implementing a root_sum function analogous to Sympy's RootSum and Maxima's similar function, whose name currently escapes me... A similar functionality exist also in Fricas, and can be met within its results.. ]

Sympy has a function Lambda, that creates a Sympy Function from a (tuple of) formal argumen(s) and a (Sympy) expression. I want to create a similar function in Sage. Such a function should:

  • accept a hold parameter (and therefore stay unevaluatd if necessary

  • accept two-ways conversions (in Sympy at first, but similar cases can be made for Mathematica and Fricas)

  • return either a lambda Python anonymous function or a Sage symbolic function.

Creating such a function is relatively easy : function (or its related sage.symbolic.function_factory.function) can almost do the necessary setup:

# Eval the rgument and return the correct symbolic function:
def eval_symbolic_lambda(self, args, body):
    from sympy.core.compatibility import iterable
    if not iterable(args): args=[args]
    sargs=tuple([u._sage_() for u in args])
    vargs=tuple([u._sage_() for u in body._sage_().variables()])
    vargs=tuple(set(vargs)-set(vargs))
    for w in sargs:
        exec("{}=SR.var('{}')".format(repr(w),repr(w)))
    for w in vargs:
        exec("{}=SR.var('{}')".format(repr(w),repr(w)))
    return eval(preparse(repr(body._sage_()))).function(*sargs)

# The symbolic functuin itself
symbolic_lambda=sage.symbolic.function_factory.function("symbolic_lambda",
                                                                                         nargs=2,
                                                                                         conversions=dict(sympy="Lambda"),
                                                                                         eval_func=eval_symbolic_lambda)

The evaluation function does its job of building the relevant symbolic function :

sage: foo=eval_symbolic_lambda(None, x,x^2+1);foo
x |--> x^2 + 1
sage: foo(3)
10
sage: foo.parent()
Callable function ring with argument x
sage: type(foo)
<class 'sage.symbolic.expression.Expression'>

The symbolic function can be held:

sage: gee=symbolic_lambda(x, x^2-1, hold=True); gee
symbolic_lambda(x, x^2 - 1)

It can also (correctly) be translated to Sympy:

sage: gee._sympy_()
Lambda(x, x**2 - 1)
sage: type(gee._sympy_())
<class 'sympy.core.function.Lambda'>

But trying to use it with evaluation fails: the result of eval_symbolic_lambda, which is a symbolic function, cannot be coerced to a symbolic expression :

sage: symbolic_lambda(x, x^2-1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/usr/local/sage-P3-2/local/lib/python3.7/site-packages/sage/libs/pynac/pynac.pyx in sage.libs.pynac.pynac.pyExpression_to_ex (build/cythonized/sage/libs/pynac/pynac.cpp:5373)()
    182     try:
--> 183         t = ring.SR.coerce(res)
    184     except TypeError as err:

/usr/local/sage-P3-2/local/lib/python3.7/site-packages/sage/structure/parent.pyx in sage.structure.parent.Parent.coerce (build/cythonized/sage/structure/parent.c:10562)()
   1113 
-> 1114     cpdef coerce(self, x):
   1115         """

/usr/local/sage-P3-2/local/lib/python3.7/site-packages/sage/structure/parent.pyx in sage.structure.parent.Parent.coerce (build/cythonized/sage/structure/parent.c:10491)()
   1143                     _record_exception()
-> 1144             raise TypeError(_LazyString(_lazy_format, ("no canonical coercion from %s to %s", parent(x), self), {}))
   1145         else:

TypeError: no canonical coercion from Callable function ring with argument x to Symbolic Ring

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-73-c250078eff24> in <module>()
----> 1 symbolic_lambda(x, x**Integer(2)-Integer(1))

/usr/local/sage-P3-2/local/lib/python3.7/site-packages/sage/symbolic/function.pyx in sage.symbolic.function.Function.__call__ (build/cythonized/sage/symbolic/function.cpp:7039)()
    600                     (<Expression>args[0])._gobj, hold)
    601         elif self._nargs == 2:
--> 602             res = g_function_eval2(self._serial, (<Expression>args[0])._gobj,
    603                     (<Expression>args[1])._gobj, hold)
    604         elif self._nargs == 3:

/usr/local/sage-P3-2/local/lib/python3.7/site-packages/sage/libs/pynac/pynac.pyx in sage.libs.pynac.pynac.pyExpression_to_ex (build/cythonized/sage/libs/pynac/pynac.cpp:5425)()
    183         t = ring.SR.coerce(res)
    184     except TypeError as err:
--> 185         raise TypeError("function did not return a symbolic expression or an element that can be coerced into a symbolic expression")
    186     return (<Expression>t)._gobj
    187 

TypeError: function did not return a symbolic expression or an element that can be coerced into a symbolic expression

Furthermore, at least in this use in "interactive mode", the mechanism allowing this function to be used for retro-converting a Sympy object to sage doesn't work:

sage: bar=sympy.Lambda(*[sympy.sympify(u) for u in (x, x^2-1)]);bar
Lambda(x, x**2 - 1)
sage: bar._sage_()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-102-5b91e5ff707e> in <module>()
----> 1 bar._sage_()

AttributeError: 'Lambda' object has no attribute '_sage_'

Any suggestion to solve those two problems ?