My favorites | Sign in
Logo
                
New issue | Search
for
| Advanced search | Search tips
Issue 252: automatic combining of exponentials and logarithms
5 people starred this issue and may be notified of changes. Back to list
Status:  Fixed
Owner:  ----
Closed:  Jun 27
Cc:  ondrej.certik
Type-Defect
Priority-Medium
WrongResult


Sign in to add a comment

Blocking:
issue 1455
 
Reported by ondrej.certik, Jul 25, 2007
I think the exponentials shouldn't be automatically combined.

Maybe this make sense:

In [1]: exp(x)*exp(-x)
Out[1]: 1

But I think all of this

In [2]: exp(x)*exp(x)
Out[2]: exp(2*x)

In [3]: exp(x)*exp(x)*exp(y)
Out[3]: exp(y + 2*x)

In [4]: exp(x)*exp(x)*exp(y)
Out[4]: exp(y + 2*x)

In [5]: exp(x)*exp(x)*exp(y)
Out[5]: exp(y + 2*x)

Should not happen automatically, because sometimes you want to have
exp(y)*exp(x) as it is. 

Also it is inconsistent with logarithms, that are not automatically combined:

In [6]: ln(x*y)
Out[6]: log(x*y)

In [7]: ln(x)+ln(y)
Out[7]: log(x) + log(y)

Comment 1 by ondrej.certik, Jul 25, 2007
See also the  issue 84 ,  issue 171  and  issue 225 .
Comment 2 by pearu.peterson, Jul 25, 2007
Combining exponentsials is a special case of combining powers:

>>> 2**x * 2 ** x
2**(2*x)
>>> 2**x * 2 ** x * 2**y
2**(y + 2*x)

So, I don't see why it should be different when base is E.

If you need exp(x+y) in the form exp(x)+exp(y) then use
as_coeff_terms() method:
>>> (exp(x)*exp(y)).as_coeff_terms()
(1, [exp(x), exp(y)])

I have noticed that when computing limits then certain
forms of an expression where exponentials are not combined,
will make the limit algorithm work. I think this is a bit
cheating, the algorithm should work with any forms of
expressions.

Hmm, may be we should combine also ln(x)+ln(y)?

Comment 3 by ondrej.certik, Jul 25, 2007
Yes, the limit algorithm depends on it. I am not sure it can be made independent of
it, because exp and log functions are cruial to it.

Yes, I also disagree with the combinign of powers. Sometimes they need to be
combined, sometimes not. The same with ln(x)+ln(y). And there should be functions
combine() and expand(), the same way as in Maple.

When I think about it, maybe the limit algorithm is an example, that it is needed to
distintuish between those two forms. 
Comment 4 by mattpap, Jul 25, 2007
I think that user should decide on the form of expressions involving powers,
exponentials and logarithms. So there should be functions expand() and combine().
However both must have parameters describing exactly their behavior eg.

  ((x + y)**2).expand() == x**2 + 2*x*y + y**2
  (sin(2*x)).expand(trig=True) == 2*sin(x)*cos(x)
  (2*(x+y)).expand(power=True) == 2**x * 2**y

or something similar.

And the limits algorithm should use some normal form of expressions (for simplicity).
Comment 5 by inferno1386, Jul 30, 2007
It's been awhile since a reply has been made, and I never really saw a clear decision
made on this. If we're not gonna change things then this issue can be closed. If we
are then I can work on this in the near future.
Comment 6 by ondrej.certik, Jul 30, 2007
We are definitely going to change things here - I think the decision can be
summarized in comment #4.
Comment 7 by inferno1386, Jul 30, 2007
Okay, well would someone else mind taking this one? I'd love to take a small break!
For fun though, here's a way to cheat the system currently:

>>> l,r = symbols('lr')
>>> exp(l(r))*l(r)/exp(l(r))
exp(l(r))*l(r)/exp(l(r))
Comment 8 by ondrej.certik, Oct 05, 2007
BTW, cheating doesn't work anymore. :)

In [2]: l,r = symbols('lr')

In [3]: exp(l(r))*l(r)/exp(l(r))
Out[3]: l(r)

Comment 9 by ondrej.certik, Oct 08, 2007
This issue should be fixed now, since other issues are depending on it.

Basically this shouldn't happen:

In [1]: log(x**2)
Out[1]: 2*log(x)

And that's because this:

In [12]: log(sin(x)/x)
Out[12]: -log(x) + log(sin(x))

causes an infinite recursion in expanding log(sin(x)), becuase it gets automatically
converted to log(x)+log(sin(x)/x) (correct) and then the second term is normally
taylor expanded, but the latter should stay as it is, but now it gets "simplified" to
-log(x)+log(sin(x)) and infinite recursion.

What's worse, due to caching and some other bug in SymPy, log(sin(x)/x) sometimes
gets expanded and sometimes not, compare:

In [1]: log(sin(x)).series(x, 2)
[...]
RuntimeError: Detected recursion while computing oseries(log(sin(x)), O(x**2))

to:

In [1]: log(sin(x)/x)
Out[1]: log(1/x*sin(x))

In [2]: log(sin(x)).series(x, 2)
Out[2]: O(x**2) + log(x)

In [3]: log(sin(x)/x)
Out[3]: log(1/x*sin(x))

In [4]: log(sin(x)).series(x, 3)
Out[4]: -1/6*x**2 + O(x**3) + log(x)

In [5]: log(sin(x)).series(x, 4)
Out[5]: -1/6*x**2 + O(x**4) + log(x)

In [6]: log(sin(x)).series(x, 5)
Out[6]: -1/6*x**2 - 1/180*x**4 + O(x**5) + log(x)
Summary: automatic combining of exponentials and logarithms
Comment 10 by ondrej.certik, Oct 08, 2007
Well, it turned out that this simple patch will prevent log(x**2) being expanded, so
I went ahead and committed it. If we ever decided it's wrong, it can easily be put back.

$ svn di
Index: sympy/integrals/tests/test_risch.py
===================================================================
--- sympy/integrals/tests/test_risch.py (revision 2518)
+++ sympy/integrals/tests/test_risch.py (working copy)
@@ -20,7 +20,7 @@
 def test_risch_norman_log():
     assert risch_norman(log(x), x) == x*log(x) - x
     assert risch_norman(log(3*x), x) == x*log(3*x) - x
-    assert risch_norman(log(x**2), x) == x*log(x**2) - 2*x
+    assert risch_norman(log(x**2), x) in [x*log(x**2) - 2*x, 2*x*log(x) - 2*x]
 
 def test_risch_norman_exp():
     assert risch_norman(exp(x), x) == exp(x)
Index: sympy/series/tests/test_limit_series.py
===================================================================
--- sympy/series/tests/test_limit_series.py     (revision 2518)
+++ sympy/series/tests/test_limit_series.py     (working copy)
@@ -255,6 +255,9 @@
     expr = exp(exp(exp(exp(x-1/exp(x)))))/exp(exp(exp(exp(x))))
     assert expr.limit(x,oo) == 0
 
+#commenting this out, because it fails due to log(x**2) not returning
+#2*log(x), see the  issue 252 
+@XFAIL
 def test_MrvLimitTestCase_page27_ex2_17():
     expr = exp(x)*(sin(1/x+exp(-x))-sin(1/x))
     assert expr.limit(x,oo) == 1
Index: sympy/functions/elementary/tests/test_exponential.py
===================================================================
--- sympy/functions/elementary/tests/test_exponential.py        (revision 2518)
+++ sympy/functions/elementary/tests/test_exponential.py        (working copy)
@@ -1,5 +1,5 @@
-
 from sympy import *
+from sympy.utilities.pytest import XFAIL
 
 def test_exp():
 
@@ -100,6 +100,8 @@
 
     assert value.epsilon_eq(Real("0.58496250072115618145373"))
 
+#commenting this out, see the  issue 252 
+@XFAIL
 def test_log_simplify():
     x = Symbol("x")
     assert log(x**2) == 2*log(x)
Index: sympy/functions/elementary/exponential.py
===================================================================
--- sympy/functions/elementary/exponential.py   (revision 2518)
+++ sympy/functions/elementary/exponential.py   (working copy)
@@ -278,10 +278,11 @@
         #using this one instead:
         elif isinstance(arg, exp):
             return arg[0]
-        elif isinstance(arg, Basic.Pow):
-            if isinstance(arg.exp, Basic.Number) or \
-               isinstance(arg.exp, Basic.NumberSymbol) or arg.exp.is_number:
-                return arg.exp * self(arg.base)
+        #this shouldn't happen automatically (see the  issue 252 ):
+        #elif isinstance(arg, Basic.Pow):
+        #    if isinstance(arg.exp, Basic.Number) or \
+        #       isinstance(arg.exp, Basic.NumberSymbol) or arg.exp.is_number:
+        #        return arg.exp * self(arg.base)
         elif isinstance(arg, Basic.Mul) and arg.is_real:
             return Basic.Add(*[self(a) for a in arg])
         elif not isinstance(arg, Basic.Add):

Comment 11 by ondrej.certik, Oct 08, 2007
Hm, but this still remains:

In [3]: x = Symbol("x", real = True)

In [4]: log(x*y)
Out[4]: log(x*y)

In [5]: y = Symbol("y", real = True)

In [6]: log(x*y)
Out[6]: log(x) + log(y)

Comment 12 by ondrej.certik, Oct 08, 2007
So that was even easier:

$ svn di
Index: sympy/functions/elementary/exponential.py
===================================================================
--- sympy/functions/elementary/exponential.py   (revision 2519)
+++ sympy/functions/elementary/exponential.py   (working copy)
@@ -283,8 +283,8 @@
         #    if isinstance(arg.exp, Basic.Number) or \
         #       isinstance(arg.exp, Basic.NumberSymbol) or arg.exp.is_number:
         #        return arg.exp * self(arg.base)
-        elif isinstance(arg, Basic.Mul) and arg.is_real:
-            return Basic.Add(*[self(a) for a in arg])
+        #elif isinstance(arg, Basic.Mul) and arg.is_real:
+        #    return Basic.Add(*[self(a) for a in arg])
         elif not isinstance(arg, Basic.Add):
             coeff = arg.as_coefficient(S.ImaginaryUnit)

Comment 13 by pvirtn, Aug 19, 2008
I think the arguments need actually to be non-negative and real for this to be a 
valid identity:

In [5]: log((-3)*(-3)).evalf()
Out[5]: 2.19722457733622

In [18]: (log(-3) + log(-3)).evalf()
Out[18]: 2.19722457733622 + 6.28318530717959⋅ⅈ

Comment 14 by ondrej.certik, Aug 19, 2008
I think so too.
Labels: WrongResult
Comment 15 by asmeurer, Jun 02, 2009
I'm going to attempt to fix this.  I will need to rewrite Mul.flatten().  I will need help fixing whatever breaks once I get the fix in.  
Also, I will need help testing to see if noncomutative stuff works, since I don't know much about that.  

By the way, I found another good reason to do this:
>>> 3**x*2**x*2**y
 y  x
2 ⋅6 
>>> 3**x*2**y*2**x
 x + y  x
2     ⋅3 
The output is inconsistent based on the order of the input, even though the input is commutative.  

It seems reasonable to me that we should still automatically combine 2**x*3**x => 6**x (when the base is a number), and, 
also, it seems we should still automatically combine exponents if the exponents are numbers, e.g., x**2*x**3 => x**5.  

Also, as mentioned above, we will need a function that does exp(x)*exp(y) => exp(x+y) and a function that does exp(x+y) => 
exp(x)*exp(y) (see  issue 1453 ).  

Any idea where those should go or what the functions should be called if they are new ones?  I like expand for the second one, 
and combine for the first one (combine would ideally reverse other things that expand does too).  
Status: Started
Cc: ondrej.certik
Labels: -Type-Enhancement Type-Defect
Comment 16 by asmeurer, Jun 02, 2009
Here is a patch that makes it not automatically combine exponents, unless the exponent would not be an Add 
(e.g., exp(x)*exp(2*x) => exp(3*x)) or if the exponent is a number (e.g., x**3*x**2 => x**5).  It DOES NOT pass 
tests (though it doesn't do bad, 11 fail and 1 exception).  Those need to be fixed.  We also need a function that 
does this (probably combine).
0001-Changed-Mul-class-so-that-it-does-not-automatically.patch
5.9 KB Download
Labels: NeedsReview
Comment 17 by asmeurer, Jun 02, 2009
Here is a patch for a combine function so that we can start to use it in the tests that the above patch makes fail.  
The function is based on the stuff that I deleted from Mul.  It also includes tests.  

Also, I forgot to mention in the last post, that patch didn't change the non-commutative part of mul, so those 
still combine automatically.  I wasn't entirely sure how to deal with those, so I left it alone.  
0001-Added-combine-function-which-combines-powers-and-ex.patch
3.6 KB Download
Comment 18 by ondrej.certik, Jun 08, 2009
Great job!

Let's get this fixed. What has to be done, so that we can start using your new
functions? Any tests still need to be fixed?
Comment 19 by asmeurer, Jun 08, 2009
I cannot get two of the failed tests in test_nseries to work.  Here is a patch that updates quite a few things here.  Hopefully I did not leave 
anything out when I commited.  

I figured out that the behavior of my combine function was already duplicated in powsimp, so I removed it.  I had to rewrite powsimp so that it 
could only expand exponentials like the way that used to be automatic before these patches.  Use powsimp(expr, deep=True, combine='exp') to 
combine expr strictly in the way that used to be automatic.  See the docstring on powsimp for more info.  

This also fixes some stuff in Mul.py that I missed the last time around.  That fixed most of the failed tests.  The remainder I have fixed by 
adding powsimp to the variables that the algorithm expects to be automatically combined, with the exception of the two tests in test_nseries, 
which, as I noted above, I have been unable to figure out.  

Please, someone help me figure this out so that I we can close this issue.  

Also, note that this patch currently depends on the distribute function that I wrote for  issue 1455 .  
0001-More-work-on-removing-automatic-combining-of-exponen.patch
26.8 KB Download
Comment 20 by Vinzent.Steinberg, Jun 09, 2009
With your patch, this could be finally fixed.

>>> (exp(x+y)).expand(power=True)
Traceback (most recent call last):
  File "/base/data/home/apps/sympy-live/1.332569642222447358/shell.py", line 296, in get
    exec compiled in statement_module.__dict__
  File "<string>", line 1, in <module>
  File "/base/data/home/apps/sympy-live/1.332569642222447358/sympy/core/basic.py",
line 1433, in expand
    expr = func()
  File "/base/data/home/apps/sympy-live/1.332569642222447358/sympy/core/basic.py",
line 1379, in _eval_expand_power
    if not isinstance(self, C.Apply):   # FIXME Apply -> Function
  File "/base/data/home/apps/sympy-live/1.332569642222447358/sympy/core/basic.py",
line 2335, in __getattr__
    raise AttributeError("No SymPy class '%s'" % name)
AttributeError: No SymPy class 'Apply'

(exp(x)*exp(y) is expected.)
Comment 21 by asmeurer, Jun 09, 2009
Actually, expand(basic) seems to expand that with my patch:
>>> print expand(exp(x+y), basic=True)
exp(x)*exp(y)

This should definitely be moved to power.  Also, there are a few things in separate() that it fixes.  You can see 
that I uncommented some doctests.
Comment 22 by Vinzent.Steinberg, Jun 17, 2009
> Use powsimp(expr, deep=True, combine='exp')

Is deep the same as recursive? If yes, we should be consistent and use only one variant.
Comment 23 by Vinzent.Steinberg, Jun 17, 2009
Do we still need your distribute()? I think it might be super-seeded by your code in
 issue 1455 .
Comment 24 by asmeurer, Jun 17, 2009
Deep is the same as recursive.  Deep is the old keyword argument from powsimp, whereas recursive is what was 
suggested for me to use with expand.  I agree that we should only use one.  Which one do you like better?
Comment 25 by asmeurer, Jun 18, 2009
I think I figured out what is wrong with my patch.  It leads to the question: do we want to automatically combine 
exp(a)*exp(b-a) => exp(b)?  And if so, where should we draw the line.  Do we want to automatically combine 
exp(a+b-c)*exp(-b+c) => exp(a) or exp(a+b-c)*exp(-b)*exp(c) => exp(a) ...  Checking all of the possible 
combinations of exponents can become costly fast.  
Labels: -NeedsReview
Comment 26 by ondrej.certik, Jun 18, 2009
I think it should not be combined by default, that's the purpose of your patch. And
when you combine it using combine(), all you have to do is to add those exponents
together and it quickly reduces to 0 (or whatever the answer is).
Comment 27 by Vinzent.Steinberg, Jun 21, 2009
I agree with Ondrej. We will have to fix the code that relies on the automatic
combining, but this should be straightforward.

'deep' is shorter to write, 'recursive' is probably correcter.  trigsimp() also uses
'recursive' iirc.
Comment 28 by asmeurer, Jun 21, 2009
I vote for deep.  separate, together, and powsimp use deep.  trigsimp uses both deep and recursive to mean 
different things.  I like deep because it is shorter and because, in my opinion, the user shouldn't need to know if 
the algorithm is implemented recursively or not.  At least that is what recursive implies to me.  I will go ahead 
and change it to deep unless there are any objections.  

As far as fixing the automatic combining code, it may be straightforward if you are familiar with the algorithm 
behind nseries.  I have been trying to fix it for days and I haven't been able to figure out what part of it relies on 
automatic combining.  If you think you know or want to take a look, I would be much obliged.  
Comment 29 by ondrej.certik, Jun 21, 2009
Let's use deep.
Comment 30 by Vinzent.Steinberg, Jun 22, 2009
I'm fine with deep. Ondrej is as far as I know the only person knowing the series  code.
Comment 31 by ondrej.certik, Jun 22, 2009
I just helped Aaron fix the series code yesterday.
Comment 32 by asmeurer, Jun 27, 2009
(No comment was entered for this change.)
Status: Fixed
Sign in to add a comment

Hosted by Google Code