| Issue 252: | automatic combining of exponentials and logarithms | |
| 5 people starred this issue and may be notified of changes. | Back to list |
Sign in to add a comment
|
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) |
||||||||||||||
,
Jul 25, 2007
See also the issue 84 , issue 171 and issue 225 . |
|||||||||||||||
,
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)? |
|||||||||||||||
,
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. |
|||||||||||||||
,
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). |
|||||||||||||||
,
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. |
|||||||||||||||
,
Jul 30, 2007
We are definitely going to change things here - I think the decision can be summarized in comment #4. |
|||||||||||||||
,
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))
|
|||||||||||||||
,
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)
|
|||||||||||||||
,
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
|
|||||||||||||||
,
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): |
|||||||||||||||
,
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)
|
|||||||||||||||
,
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) |
|||||||||||||||
,
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⋅ⅈ |
|||||||||||||||
,
Aug 19, 2008
I think so too.
Labels: WrongResult
|
|||||||||||||||
,
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 |
|||||||||||||||
,
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).
Labels: NeedsReview
|
|||||||||||||||
,
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. |
|||||||||||||||
,
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? |
|||||||||||||||
,
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 . |
|||||||||||||||
,
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.)
|
|||||||||||||||
,
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. |
|||||||||||||||
,
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. |
|||||||||||||||
,
Jun 17, 2009
Do we still need your distribute()? I think it might be super-seeded by your code in issue 1455 . |
|||||||||||||||
,
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? |
|||||||||||||||
,
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
|
|||||||||||||||
,
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). |
|||||||||||||||
,
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. |
|||||||||||||||
,
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. |
|||||||||||||||
,
Jun 21, 2009
Let's use deep. |
|||||||||||||||
,
Jun 22, 2009
I'm fine with deep. Ondrej is as far as I know the only person knowing the series code. |
|||||||||||||||
,
Jun 22, 2009
I just helped Aaron fix the series code yesterday. |
|||||||||||||||
,
Jun 27, 2009
(No comment was entered for this change.)
Status: Fixed
|
|||||||||||||||
|
|
|||||||||||||||