# How to wrap a sympy function as a sage function ?

Inspired by a recent question :

importing a function from sympy allows apparently to use a sympy function in sage:

sage: import sympy
sage: from sympy import sympify, sin as ssin
sage: ssin(sympify(a+b))
sin(a + b)


But this is purely cosmetic : this result does not have the methods of a Sage symbolic expression:

sage: ssin(sympify(a+b)).trig_expand()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-859c8e851702> in <module>()
----> 1 ssin(sympify(a+b)).trig_expand()

AttributeError: 'sin' object has no attribute 'trig_expand'
sage: ssin(sympify(a+b)).operator()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-16-d52ecf6bce28> in <module>()
----> 1 ssin(sympify(a+b)).operator()

AttributeError: 'sin' object has no attribute 'operator'


In fact, this is a sympy object :

sage: type(ssin(sympify(a+b)))
sin


whereas:

sage: type(sin(a+b))
<class 'sage.symbolic.expression.Expression'>


So, my question is :how toi create a Sage function that :

• translates all its argument to the relevant sympy types

• calls somehow the sympy functin on these arguments

• translates back the result to Sage ?

This is done for some Sage functions, implemented by Maxima or sympy, in the Sage library. Is it possible to make this "lightly" at run time in some Sage source code without having to recompile the Sage library ?

edit retag close merge delete

Sort by » oldest newest most voted

Here is an attempt:

def my_lerch_phi_evalf(self, z, s, a, parent=None, algorithm=None):
if parent is None:
return self(z, s, a)._sympy_().evalf()._sage_()
else:
digits = ceil(log(float(2), float(10)) * parent.prec())
return self(z, s, a)._sympy_().evalf(n=digits)._sage_()

my_lerch_phi = function('my_lerch_phi', nargs=3,
conversions=dict(sympy='lerchphi'),
evalf_func=my_lerch_phi_evalf)


Now we can convert our function to SymPy:

sage: _ = var('z,s,a')
sage: f = my_lerch_phi(z, s, a); f
my_lerch_phi(z, s, a)
sage: f._sympy_()
lerchphi(z, s, a)
sage: unicode_art(f)
Φ(z, s, a)


and evaluate it numerically:

sage: my_lerch_phi(1, 2, 3).n()
0.394934066848226
sage: my_lerch_phi(1, 2, 3).n(100)
0.394934066848226436472415166646
sage: my_lerch_phi(11/10, 2, 3).n()
0.420359955902005 - 0.224963005774297*I


Note that the precision is a bit higher than needed because we rounded up, so some more adjustments are needed to convert to the parent of the correct precision. Moreover, function has several more optional arguments that may be useful.

For the conversion from SymPy to Sage, one could use this hack which is based on the current implementation of _sympysage_function. (It might be better if the SymPy interface made use of the symbol_table for the conversion to Sage, like the Mathematica interface does, which allows for customization.)

sage: f._sympy_()._sage_()
...
AttributeError:
sage: sage.functions.all.lerchphi = my_lerch_phi  # this is a hack
sage: f._sympy_()._sage_()  # now this works
my_lerch_phi(z, s, a)


Now also this gives a correct error message:

sage: my_lerch_phi(z, s, a).n()
...
TypeError: cannot evaluate symbolic expression numerically

more

It might be useful if the SymPy interface attempted to do all of this automatically, i.e. whenever there is no corresponding Sage function when converting to Sage, the interface could create such a wrapper on the fly, so that integrate(..., algorithm='sympy') produces useful results instead of raising an error.

( 2020-07-11 14:35:36 +0200 )edit

I was thinking more along the lines of symbolic functions, in order to benefit from the "know-how" accumulated in sympy's code... Numerical computation is but one of the goals.

Automatic import is tempting, but I see at least two possible snags :

• Argument lists : this automatic conversion might only handle unambiguous mandatory argument lists ; optional and keyword arguments might be trickier to handle.

• global namespace cluttering : such a conversion should be memorized in order to alleviate repeated evaluation. The onluy logical space for this is the global namespace. But I'm wary of accumulating garbage...

I have to think this over. Would you mind comment on this comment to let me know further t

( 2020-07-11 20:37:12 +0200 )edit

The function factory returns a subtype of SymbolicFunction. It is possible to customize the symbolic evaluation as well:

def my_lerch_phi_eval(self, *args):
import sympy
f = sympy.lerchphi(*(a._sympy_() for a in args))
g = sympy.expand_func(f)
return None if f == g else g._sage_()

my_lerch_phi = function('my_lerch_phi', nargs=3,
conversions=dict(sympy='lerchphi'),
eval_func=my_lerch_phi_eval,
evalf_func=my_lerch_phi_evalf)


Example:

sage: _ = var('z,s,a')
sage: my_lerch_phi(z, s, a) * my_lerch_phi(z, s, 1)
my_lerch_phi(z, s, a)*polylog(s, z)/z

( 2020-07-13 20:38:55 +0200 )edit

However, this seems to be much more difficult to implement generically, for arbitrary SymPy functions. In SymPy, expand_func is not applied automatically, so Sage should probably not do this either, but the functionality of expand_func does not seem to be accessible from within Sage.

Without defining eval_func, but just evalf_func, I think that such functions could be created on the fly. They could be cached, but do not need to be added to the global namespace, as they do not have any symbolic capabilities. Basically, it would just make integration return correct results that can also be evaluated numerically.

( 2020-07-13 20:39:11 +0200 )edit

Hello, @Emmanuel Charpentier! I am not 100% sure if this is exactly what you are looking for, but there exist these methods called _sympy_() and _sage_(). The first one converts some Sage expression to Sympy, while the second converts Maxima/Sympy/Giac/etc. expressions to Sage.

For example, I can wrap your code inside a function like this:

def sympy_sin(a, b):
# Convert to Sympy (this renders the sympify method unnecessary in this case)
sa = a._sympy_()
sb = b._sympy_()

from sympy import sin as ssin
res = ssin(sa + sb)
# Convert back to Sage and return result
return res._sage_()


Now, you could do something like the following:

var('a b')
ss = sympy_sin(a, b)
ss.trig_expand()


and you will get the correct answer, cos(b)*sin(a) + cos(a)*sin(b). To further verify the conversion step, we do

type(ss)


and we get the expected <class 'sage.symbolic.expression.Expression'>.

Of course, it is NOT necessary to wrap this into a function, like I did above; you can use these methods in any place on Sage.

I hope this helps!

more

I'm looking to use these wraopers precisely in cases where the sumpy finctins of interest have NO _sage_() method nor Sage equivalent... precisely to give Sage the related abilities. Look at the quiestion I pointed to for an example of what I mean to do...

( 2020-07-11 00:01:17 +0200 )edit

This is what I have asked elsewhere. Apparently the sympy "integrate()" function has functionality that Sagemath hasn't implemented; even with 'algorithm = "sympy" ' . In addition, "from sympy import * " changes the syntax of Sagemath "integrate" including limit formatting.
I will try your wrapper as a way to access this functionality although it would be more consistent to go through "algorithm='sympy' " for integration; and have the limit formatting done automatically; the same for mathematica expressions.

( 2020-07-11 14:10:51 +0200 )edit