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
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
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?
--
Всего хорошего, Кирилл.
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!
--
Всего хорошего, Кирилл.
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
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
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.
--
Всего хорошего, Кирилл.
Thanks!
For example ssh-agent prints sh-source-lines which when sourced set up
correct environment.
Maybe sage has something similiar?
--
Всего хорошего, Кирилл.
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
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
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.
--
Всего хорошего, Кирилл.
How about sage-env?
http://hg.sagemath.org/scripts-main/file/tip/sage-env
(or am I wrong?)
--
Всего хорошего, Кирилл.
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
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
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.
--
Всего хорошего, Кирилл.
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?
--
Всего хорошего, Кирилл.
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
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
Could you please try this:
$ . sage-env
$ python
>>> import sage.all
?
--
Всего хорошего, Кирилл.
Ah, I see. Sorry for misinformation.
--
Всего хорошего, Кирилл.
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
Nevermind, it happens with all of us :)
--
Всего хорошего, Кирилл.
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
Yes, I'm ok -- please go ahead, and thanks for the patch.
--
Всего хорошего, Кирилл.
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