[PATCH 0 of 2] SymPy/Sage interaction improved a lot

4 views
Skip to first unread message

Ondrej Certik

unread,
Jul 19, 2008, 7:26:21 PM7/19/08
to sympy-...@googlegroups.com
Please review especially the way Sage is invoked in the test.

Ondrej Certik

unread,
Jul 19, 2008, 7:26:23 PM7/19/08
to sympy-...@googlegroups.com
# HG changeset patch
# User Ondrej Certik <ond...@certik.cz>
# Date 1216509887 -7200
# Node ID 081c561d31546f8b7ce1beb5ac71edcc410bef7e
# Parent 7d8d2a7448658962eafee966310d527f0e9e2ced
Thorough tests for Sage <--> SymPy interaction added.

diff --git a/sympy/test_external/test_sage.py b/sympy/test_external/test_sage.py
new file mode 100644
--- /dev/null
+++ b/sympy/test_external/test_sage.py
@@ -0,0 +1,144 @@
+# This testfile tests SymPy <-> Sage compatibility
+
+# Don't test any SymPy features here. Just pure interaction with Sage.
+# Always write regular SymPy tests for anything, that can be tested in pure
+# Python (without Sage). Here we test everything, that a user may need when
+# using SymPy with Sage.
+
+import os
+import re
+import sys
+
+def set_paths(root="/home/ondra/ext/sage"):
+ """
+ Set paths necessary to import sage.
+ root ... the path to your Sage installation
+ """
+ python_path = [
+ root+"/local/lib/python",
+ root+"/local/lib/python2.5/lib-dynload",
+ root+"/local/lib/python2.5/site-packages",
+ ]
+ sys.path = python_path + sys.path
+ paths = {
+ "SAGE_ROOT": root,
+ "SAGE_LOCAL": root+"/local",
+ "DOT_SAGE": "/tmp/dot_sage/",
+ "SAGE_SERVER": "http://www.sagemath.org/",
+ "PATH": root+"/local/bin:"+os.environ["PATH"],
+ }
+ os.environ.update(paths)
+
+set_paths()
+
+# This hack is needed, otherwise Sage fails to import. See this thread for more
+# info:
+#http://groups.google.com/group/sage-devel/browse_thread/thread/cf21fab7e612112b
+import sys
+if not hasattr(sys.stdin, "write"):
+ sys.stdin.write = 1
+ sys.stdin.flush = 1
+
+try:
+ import sage.all as sage
+except ImportError:
+ #py.test will not execute any tests now
+ disabled = True
+
+import sympy
+
+def check_expression(expr, var_symbols):
+ """Does eval(expr) both in Sage and SymPy and does other checks."""
+
+ # evaluate the expression in the context of Sage:
+ sage.var(var_symbols)
+ a = globals().copy()
+ # safety checks...
+ assert not "sin" in a
+ a.update(sage.__dict__)
+ assert "sin" in a
+ e_sage = eval(expr, a)
+ assert not isinstance(e_sage, sympy.Basic)
+
+ # evaluate the expression in the context of SymPy:
+ sympy.var(var_symbols)
+ b = globals().copy()
+ assert not "sin" in b
+ b.update(sympy.__dict__)
+ assert "sin" in b
+ b.update(sympy.__dict__)
+ e_sympy = eval(expr, b)
+ assert isinstance(e_sympy, sympy.Basic)
+
+ # Do the actual checks:
+ assert sympy.sympify(e_sage) == e_sympy
+ assert e_sage == sage.SR(e_sympy)
+
+
+
+def test_basics():
+ check_expression("x", "x")
+ check_expression("x**2", "x")
+ check_expression("x**2+y**3", "x y")
+ check_expression("1/(x+y)**2-x**3/4", "x y")
+
+def test_complex():
+ check_expression("23+I*4", "x")
+ check_expression("x+I*y", "x y")
+
+def test_E():
+ assert sympy.sympify(sage.e) == sympy.E
+ assert sage.e == sage.SR(sympy.E)
+
+def test_pi():
+ assert sympy.sympify(sage.pi) == sympy.pi
+ assert sage.pi == sage.SR(sympy.pi)
+
+def test_euler_gamma():
+ assert sympy.sympify(sage.euler_gamma) == sympy.EulerGamma
+ assert sage.euler_gamma == sage.SR(sympy.EulerGamma)
+
+def test_oo():
+ assert sympy.sympify(sage.oo) == sympy.oo
+ assert sage.oo == sage.SR(sympy.oo)
+
+def test_NaN():
+ assert sympy.sympify(sage.NaN) == sympy.nan
+ assert sage.NaN == sage.SR(sympy.nan)
+
+def test_Catalan():
+ assert sympy.sympify(sage.catalan) == sympy.Catalan
+ assert sage.catalan == sage.SR(sympy.Catalan)
+
+def test_GoldenRation():
+ assert sympy.sympify(sage.golden_ratio) == sympy.GoldenRatio
+ assert sage.golden_ratio == sage.SR(sympy.GoldenRatio)
+
+def test_functions():
+ check_expression("sin(x)", "x")
+ check_expression("cos(x)", "x")
+ check_expression("tan(x)", "x")
+ check_expression("cot(x)", "x")
+ check_expression("asin(x)", "x")
+ check_expression("acos(x)", "x")
+ check_expression("atan(x)", "x")
+ check_expression("acot(x)", "x")
+ check_expression("sinh(x)", "x")
+ check_expression("cosh(x)", "x")
+ check_expression("tanh(x)", "x")
+ check_expression("coth(x)", "x")
+ check_expression("asinh(x)", "x")
+ check_expression("acosh(x)", "x")
+ check_expression("atanh(x)", "x")
+ check_expression("acoth(x)", "x")
+ check_expression("exp(x)", "x")
+ check_expression("log(x)", "x")
+ check_expression("abs(x)", "x")
+
+def test_issue924():
+ sage.var("a x")
+ log = sage.log
+ i = sympy.integrate(log(x)/a, (x, a, a+1))
+ i2 = sympy.simplify(i)
+ s = sage.SR(i2)
+ assert s == (a*log(1+a) - a*log(a) + log(1+a) - 1)/a

Ondrej Certik

unread,
Jul 19, 2008, 7:26:22 PM7/19/08
to sympy-...@googlegroups.com
# HG changeset patch
# User Ondrej Certik <ond...@certik.cz>
# Date 1216509886 -7200
# Node ID 7d8d2a7448658962eafee966310d527f0e9e2ced
# Parent a4e0211efb953ac275a9643e17cf6ff242f583e4
_sage_() methods implemented in all common classes.

The thorough tests for these methods is in the next patch.

diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -1056,6 +1056,10 @@ class Infinity(Singleton, Rational):
def _as_mpf_val(self, prec):
return mlib.finf

+ def _sage_(self):


+ import sage.all as sage

+ return sage.oo
+
class NegativeInfinity(Singleton, Rational):

p = -1
@@ -1130,6 +1134,10 @@ class NaN(Singleton, Rational):
if e is S.Zero:
return S.One
return b
+
+ def _sage_(self):


+ import sage.all as sage

+ return sage.NaN

class ComplexInfinity(Singleton, Atom, NoRelMeths, ArithMeths):

@@ -1268,6 +1276,10 @@ class Exp1(NumberSymbol):
def _eval_power(self, exp):
return C.exp(exp)

+ def _sage_(self):


+ import sage.all as sage

+ return sage.e
+
class Pi(NumberSymbol):

is_real = True
@@ -1293,6 +1305,10 @@ class Pi(NumberSymbol):
def tostr(self, level=0):
return 'pi'

+ def _sage_(self):


+ import sage.all as sage

+ return sage.pi
+
class GoldenRatio(NumberSymbol):

is_real = True
@@ -1317,6 +1333,10 @@ class GoldenRatio(NumberSymbol):
def tostr(self, level=0):
return 'GoldenRatio'

+ def _sage_(self):


+ import sage.all as sage

+ return sage.golden_ratio
+
class EulerGamma(NumberSymbol):

is_real = True
@@ -1338,6 +1358,10 @@ class EulerGamma(NumberSymbol):
def tostr(self, level=0):
return 'EulerGamma'

+ def _sage_(self):


+ import sage.all as sage

+ return sage.euler_gamma
+
class Catalan(NumberSymbol):

is_real = True
@@ -1358,6 +1382,10 @@ class Catalan(NumberSymbol):

def tostr(self, level=0):
return 'Catalan'
+
+ def _sage_(self):


+ import sage.all as sage

+ return sage.catalan

class ImaginaryUnit(Singleton, Atom, RelMeths, ArithMeths):

@@ -1412,6 +1440,10 @@ class ImaginaryUnit(Singleton, Atom, Rel

def as_base_exp(self):
return -S.One, S.Half
+
+ def _sage_(self):


+ import sage.all as sage

+ return sage.I

# /cyclic/
import basic as _
diff --git a/sympy/functions/elementary/complexes.py b/sympy/functions/elementary/complexes.py
--- a/sympy/functions/elementary/complexes.py
+++ b/sympy/functions/elementary/complexes.py
@@ -236,6 +236,10 @@ class abs(Function):
return self.args[0]**other
return

+ def _sage_(self):


+ import sage.all as sage

+ return sage.abs_symbolic(self.args[0]._sage_())
+
class arg(Function):

nargs = 1
diff --git a/sympy/functions/elementary/exponential.py b/sympy/functions/elementary/exponential.py
--- a/sympy/functions/elementary/exponential.py
+++ b/sympy/functions/elementary/exponential.py
@@ -251,7 +251,7 @@ class exp(Function):

def _sage_(self):
import sage.all as sage
- return sage.exp(self[0]._sage_())
+ return sage.exp(self.args[0]._sage_())

class log(Function):

@@ -441,7 +441,7 @@ class log(Function):

def _sage_(self):
import sage.all as sage
- return sage.log(self[0]._sage_())
+ return sage.log(self.args[0]._sage_())

# MrvLog is used by limit.py
class MrvLog(log):
diff --git a/sympy/functions/elementary/hyperbolic.py b/sympy/functions/elementary/hyperbolic.py
--- a/sympy/functions/elementary/hyperbolic.py
+++ b/sympy/functions/elementary/hyperbolic.py
@@ -105,6 +105,10 @@ class sinh(Function):
if arg.is_imaginary:
return True

+ def _sage_(self):


+ import sage.all as sage

+ return sage.sinh(self.args[0]._sage_())
+
class cosh(Function):

nargs = 1
@@ -200,6 +204,10 @@ class cosh(Function):
arg = self.args[0]
if arg.is_imaginary:
return True
+
+ def _sage_(self):


+ import sage.all as sage

+ return sage.cosh(self.args[0]._sage_())

class tanh(Function):

@@ -299,6 +307,10 @@ class tanh(Function):
if arg.is_real:
return True

+ def _sage_(self):


+ import sage.all as sage

+ return sage.tanh(self.args[0]._sage_())
+
class coth(Function):

nargs = 1
@@ -389,6 +401,10 @@ class coth(Function):
else:
return self.func(arg)

+ def _sage_(self):


+ import sage.all as sage

+ return sage.coth(self.args[0]._sage_())
+

###############################################################################
############################# HYPERBOLIC INVERSES #############################
@@ -465,6 +481,10 @@ class asinh(Function):
else:
return self.func(arg)

+ def _sage_(self):


+ import sage.all as sage

+ return sage.asinh(self.args[0]._sage_())
+
class acosh(Function):

nargs = 1
@@ -540,6 +560,10 @@ class acosh(Function):
else:
return self.func(arg)

+ def _sage_(self):


+ import sage.all as sage

+ return sage.acosh(self.args[0]._sage_())
+
class atanh(Function):

nargs = 1
@@ -596,6 +620,10 @@ class atanh(Function):
return arg
else:
return self.func(arg)
+
+ def _sage_(self):


+ import sage.all as sage

+ return sage.atanh(self.args[0]._sage_())

class acoth(Function):

@@ -660,3 +688,6 @@ class acoth(Function):
else:
return self.func(arg)

+ def _sage_(self):


+ import sage.all as sage

+ return sage.acoth(self.args[0]._sage_())
diff --git a/sympy/functions/elementary/trigonometric.py b/sympy/functions/elementary/trigonometric.py
--- a/sympy/functions/elementary/trigonometric.py
+++ b/sympy/functions/elementary/trigonometric.py
@@ -535,6 +535,10 @@ class tan(Function):
if arg.is_imaginary:
return True

+ def _sage_(self):


+ import sage.all as sage

+ return sage.tan(self.args[0]._sage_())
+
class cot(Function):
"""
Usage
@@ -652,6 +656,10 @@ class cot(Function):

def _eval_is_real(self):
return self.args[0].is_real
+
+ def _sage_(self):


+ import sage.all as sage

+ return sage.cot(self.args[0]._sage_())

###############################################################################
########################### TRIGONOMETRIC INVERSES ############################
@@ -750,6 +758,10 @@ class asin(Function):
def _eval_is_real(self):
return self.args[0].is_real and (self.args[0]>=-1 and self.args[0]<=1)

+ def _sage_(self):


+ import sage.all as sage

+ return sage.asin(self.args[0]._sage_())
+
class acos(Function):
"""
Usage
@@ -832,6 +844,10 @@ class acos(Function):

def _eval_is_real(self):
return self.args[0].is_real and (self.args[0]>=-1 and self.args[0]<=1)
+
+ def _sage_(self):


+ import sage.all as sage

+ return sage.acos(self.args[0]._sage_())

class atan(Function):
"""
@@ -915,6 +931,10 @@ class atan(Function):
def _eval_is_real(self):
return self.args[0].is_real

+ def _sage_(self):


+ import sage.all as sage

+ return sage.atan(self.args[0]._sage_())
+
class acot(Function):
"""
Usage
@@ -995,6 +1015,10 @@ class acot(Function):
def _eval_is_real(self):
return self.args[0].is_real

+ def _sage_(self):


+ import sage.all as sage

+ return sage.acot(self.args[0]._sage_())
+
class atan2(Function):
"""
Returns the atan(y/x) taking two arguments y and x. Signs of

Kirill Smelkov

unread,
Jul 20, 2008, 2:08:57 AM7/20/08
to sympy-...@googlegroups.com

Looks ok, although I'd put it in one place (Basic._sage_) and build a
map of correspondence between SymPy classes and Sage classes.

Btw, this map could be handy for implementing _sympy_ method in Sage to
map back Sage objects to SymPy, so

This way we'll control both conversions mostly in one place.

What do you think?

--
Всего хорошего, Кирилл.

Kirill Smelkov

unread,
Jul 20, 2008, 3:41:45 AM7/20/08
to sympy-...@googlegroups.com

Is this neccessary?

I thought that when one installs sage, it could put such setting into
say sage.env and source that sage.env from profile, so that when this
test runs it will detect that sage.

(I can't investigate because I don't have sage installed)

Otherwise looks good, thanks!

--
Всего хорошего, Кирилл.

Ondrej Certik

unread,
Jul 20, 2008, 7:27:50 AM7/20/08
to sympy-...@googlegroups.com

Yes, I think we should do that. But creating such env by hand sucks,
so I'll create a script that we'll put to bin/, that will be called
with the patch of the Sage install and it will return the correct
setup.

I'll rework the patches, thanks!

Ondrej

Ondrej Certik

unread,
Jul 20, 2008, 7:32:52 AM7/20/08
to sympy-...@googlegroups.com

Yes, I think it's a good idea to have this at one place. I'll do that.

It's always a question, whether things should be at one place, or
local to each sympy class. E.g. we refactored printing so that it is
at one place. Now we'll do the same with _sage_. Well, what about
other things? Maybe we can do that with everything?

Think about this:

* series expansion
* canonicalize (?)
* _eval_rewrite and stuff


I don't have the answer. In the case of Sage, my intuition says, let's
put it at one place in Basic.

Ondrej

Kirill Smelkov

unread,
Jul 20, 2008, 7:35:55 AM7/20/08
to sympy-...@googlegroups.com

Thanks.

> It's always a question, whether things should be at one place, or
> local to each sympy class. E.g. we refactored printing so that it is
> at one place. Now we'll do the same with _sage_. Well, what about
> other things? Maybe we can do that with everything?
>
> Think about this:
>
> * series expansion
> * canonicalize (?)
> * _eval_rewrite and stuff
>
>
> I don't have the answer. In the case of Sage, my intuition says, let's
> put it at one place in Basic.

I don't have the answer either, just in _sage_ case all methods were
very similiar.

And also I believe that

"complexity has to be localized."

Because then, you are able to do all the tricky things without patching
all the place -- when complexity is localized, it is easier to improve.

--
Всего хорошего, Кирилл.

Kirill Smelkov

unread,
Jul 20, 2008, 7:37:59 AM7/20/08
to sympy-...@googlegroups.com

Thanks!

For example ssh-agent prints sh-source-lines which when sourced set up
correct environment.

Maybe sage has something similiar?

--
Всего хорошего, Кирилл.

Ondrej Certik

unread,
Jul 20, 2008, 7:54:25 AM7/20/08
to sympy-...@googlegroups.com
>>
>> Think about this:
>>
>> * series expansion
>> * canonicalize (?)
>> * _eval_rewrite and stuff
>>
>>
>> I don't have the answer. In the case of Sage, my intuition says, let's
>> put it at one place in Basic.
>
> I don't have the answer either, just in _sage_ case all methods were
> very similiar.
>
> And also I believe that
>
> "complexity has to be localized."
>
> Because then, you are able to do all the tricky things without patching
> all the place -- when complexity is localized, it is easier to improve.

Yes, this is the driving idea. However, if you add a new class to
sympy, you then need to patch all the place -- Basic._sage_, all the
printing classes,... So in this sense, the complexity is not
localized.

Ondrej

Ondrej Certik

unread,
Jul 20, 2008, 7:55:59 AM7/20/08
to sympy-...@googlegroups.com
On Sun, Jul 20, 2008 at 1:37 PM, Kirill Smelkov

They don't, that's why I want to write it. We'll have it in sympy,
until it gets accepted to Sage. They start it with the "sage" command,
but that runs their own version of Python and other libraries. So this
is not the way.

Ondrej

Kirill Smelkov

unread,
Jul 20, 2008, 8:10:08 AM7/20/08
to sympy-...@googlegroups.com

In Basic._sage_ case you'll just need to add an entry to class-names
mapping table between SymPy and Sage, (and btw we could make the default
1-to-1, e.g. 'sin' <-> 'sin' does not need an entry.)

All the actual mapping complexity sits in Basic._sage_ which does object
conversion.

And also the complexity is localized in a sence, that to get going with
new SymPy class one doesn't need to write all that _latex_, _sage_,
_etc_, methods -- we'll just have the basic functionality (writen by one
person), and then other subsystems maintainers could add appropriate
bits to their stuff.

Otherwise one person has to know all the details about all kind of
interactions between SymPy and external software.

--
Всего хорошего, Кирилл.

Kirill Smelkov

unread,
Jul 20, 2008, 8:18:25 AM7/20/08
to sympy-...@googlegroups.com

How about sage-env?

http://hg.sagemath.org/scripts-main/file/tip/sage-env

(or am I wrong?)

--
Всего хорошего, Кирилл.

Ondrej Certik

unread,
Jul 20, 2008, 8:24:02 AM7/20/08
to sympy-...@googlegroups.com

Yes. So, is there any argument to keep let's say rewriting stuff
scattered all over classes? Is there actually anything that needs to
be scattered all over classes?

I am all of subsystem maintainers, that's a good way to maintain things.

Ondrej

Ondrej Certik

unread,
Jul 20, 2008, 8:27:46 AM7/20/08
to sympy-...@googlegroups.com
On Sun, Jul 20, 2008 at 2:18 PM, Kirill Smelkov

It doesn't quite work. We need to import Sage in a systemwide python.
The above seems to only setup paths to run sage from sage's python.

Ondrej

Kirill Smelkov

unread,
Jul 20, 2008, 8:28:12 AM7/20/08
to sympy-...@googlegroups.com

I can't say about rewrite becase at the moment I don't have good idea
about what rewrite does.

(and btw, at present there are only two Basic._eval_rewrite and
Function._eval_rewrite)

> I am all of subsystem maintainers, that's a good way to maintain things.

Yes. On the other hand integration is important too, so I think a good
compromise would do.

--
Всего хорошего, Кирилл.

Kirill Smelkov

unread,
Jul 20, 2008, 8:33:26 AM7/20/08
to sympy-...@googlegroups.com

It sets PYTHONPATH:

http://hg.sagemath.org/scripts-main/file/6740a7e39830/sage-env (line 137)

and LD_LIBRARY_PATH (line 150)

Isn't this what we need?

--
Всего хорошего, Кирилл.

Ondrej Certik

unread,
Jul 20, 2008, 9:48:44 AM7/20/08
to sympy-...@googlegroups.com
On Sun, Jul 20, 2008 at 2:28 PM, Kirill Smelkov

No, there are dozens more, see e.g.: trigonometric.py

def _eval_rewrite_as_exp(self, arg):
exp, I = C.exp, S.ImaginaryUnit
return (exp(arg*I) - exp(-arg*I)) / (2*I)

def _eval_rewrite_as_cos(self, arg):
return -cos(arg + S.Pi/2)

def _eval_rewrite_as_tan(self, arg):
tan_half = tan(S.Half*arg)
return 2*tan_half/(1 + tan_half**2)

def _eval_rewrite_as_cot(self, arg):
cot_half = S.Cot(S.Half*arg)
return 2*cot_half/(1 + cot_half**2)


Ondrej

Ondrej Certik

unread,
Jul 20, 2008, 9:49:39 AM7/20/08
to sympy-...@googlegroups.com
On Sun, Jul 20, 2008 at 2:33 PM, Kirill Smelkov

yes, but we need

$ bash sage-env
$ python
>>> import sage.all

And that doesn't work. It's easy to do so however and I'll send a
patch with such a script.

Ondrej

Kirill Smelkov

unread,
Jul 20, 2008, 10:00:38 AM7/20/08
to sympy-...@googlegroups.com

Could you please try this:

$ . sage-env
$ python
>>> import sage.all

?

--
Всего хорошего, Кирилл.

Kirill Smelkov

unread,
Jul 20, 2008, 10:01:04 AM7/20/08
to sympy-...@googlegroups.com

Ah, I see. Sorry for misinformation.

--
Всего хорошего, Кирилл.

Ondrej Certik

unread,
Jul 20, 2008, 10:18:07 AM7/20/08
to sympy-...@googlegroups.com
On Sun, Jul 20, 2008 at 4:00 PM, Kirill Smelkov


Ok, I was stupid, that of course works. Sorry for the noise.

ondra@fuji:~/ext/sage$ . local/bin/sage-env
ondra@fuji:~/ext/sage$ python
Python 2.5.2 (r252:60911, Jul 11 2008, 05:28:36)
[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sage.all
>>>


Ondrej

Kirill Smelkov

unread,
Jul 20, 2008, 10:20:26 AM7/20/08
to sympy-...@googlegroups.com

Nevermind, it happens with all of us :)

--
Всего хорошего, Кирилл.

Ondrej Certik

unread,
Jul 21, 2008, 6:49:42 AM7/21/08
to sympy-...@googlegroups.com
> Looks ok, although I'd put it in one place (Basic._sage_) and build a
> map of correspondence between SymPy classes and Sage classes.
>
> Btw, this map could be handy for implementing _sympy_ method in Sage to
> map back Sage objects to SymPy, so
>
> This way we'll control both conversions mostly in one place.
>
> What do you think?

Sorry, running out of time. Is it ok to create an issue for this and
rework it later, or are you against? This is a blocker for the
release.

Ondrej

Kirill Smelkov

unread,
Jul 21, 2008, 7:54:37 AM7/21/08
to sympy-...@googlegroups.com

Yes, I'm ok -- please go ahead, and thanks for the patch.

--
Всего хорошего, Кирилл.

Ondrej Certik

unread,
Jul 21, 2008, 7:29:47 PM7/21/08
to sympy-...@googlegroups.com

Ondrej Certik

unread,
Jul 21, 2008, 7:49:34 PM7/21/08
to sympy-...@googlegroups.com
On Sun, Jul 20, 2008 at 4:20 PM, Kirill Smelkov

Unfortunately this environment doesn't include system paths, so
py.test fails to import. So I just left my original setup in there,
which I know it works. Further improvements would be useful of course.

Ondrej

Reply all
Reply to author
Forward
0 new messages