Skip to content

Conversation

oscargus
Copy link
Contributor

@oscargus oscargus commented Aug 21, 2021

References to other Issues or PRs

Closes #10085 (added test, solved elsewhere)
Closes #10876 (added test, solved elsewhere)
Closes #12032 (added test, solved elsewhere)
Fixes #13396 (solved underlying problem)
Added test for #13961 (gives two identical solutions)
Closes #14541 (added test, solved elsewhere)
Closes #14642 (added test, solved elsewhere)
Added test for #16643 (original issue solved, but not follow-up equation)
Fixes #17580 (solved underlying problem)
Closes #17906 (added test, solved elsewhere(?))
Fixes #17940 (solved underlying problem)
Fixes #19506 (solved underlying problem)

Brief description of what is fixed or changed

Added tests and closed where appropriate.

Other comments

Release Notes

  • solvers
    • solveset correctly solves arg when the argument is complex.

@sympy-bot
Copy link

sympy-bot commented Aug 21, 2021

Hi, I am the SymPy bot (v161). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

Your release notes are in good order.

Here is what the release notes will look like:

  • solvers
    • solveset correctly solves arg when the argument is complex. (#21925 by @oscargus)

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.10.

Click here to see the pull request description that was parsed.
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->

Closes #10085 (added test, solved elsewhere)
Closes #10876 (added test, solved elsewhere)
Closes #12032 (added test, solved elsewhere)
Fixes #13396 (solved underlying problem)
Added test for #13961 (gives two identical solutions)
Closes #14541 (added test, solved elsewhere)
Closes #14642 (added test, solved elsewhere)
Added test for #16643 (original issue solved, but not follow-up equation)
Fixes #17580 (solved underlying problem)
Closes #17906 (added test, solved elsewhere(?))
Fixes #17940 (solved underlying problem)
Fixes #19506 (solved underlying problem)



#### Brief description of what is fixed or changed
Added tests and closed where appropriate.

#### Other comments

#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers.

or if no release note(s) should be included use:

NO ENTRY

See https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more
information on how to write release notes. The bot will check your release
notes automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* solvers
    * `solveset` correctly solves `arg` when the argument is complex.

<!-- END RELEASE NOTES -->

Update

The release notes on the wiki have been updated.

@@ -1724,6 +1731,130 @@ def test_solve_nonlinear_trans():
assert nonlinsolve([x**2 - y**2/exp(x)], [x, y]) == soln4


def test_issue_14642():
x = symbols('x',dtype = complex)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does dtype=complex do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea. The original code was like this...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is supposed to be complex=True or something. It should be removed though because it doesn't do anything (ideally this would raise an error).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! Will do!

@oscargus
Copy link
Contributor Author

I've updated the tests and original comment so that the status is correct.

@oscargus oscargus changed the title Added tests for #10086, #10876, #12032, #13396, #13961, #14541, #14642 Added tests for #10085, #10876, #12032, #13396, #13961, #14541, #14642 Aug 25, 2021
@github-actions
Copy link

github-actions bot commented Aug 25, 2021

Benchmark results from GitHub Actions

Lower numbers are good, higher numbers are bad. A ratio less than 1
means a speed up and greater than 1 means a slowdown. Green lines
beginning with + are slowdowns (the PR is slower then master or
master is slower than the previous release). Red lines beginning
with - are speedups.

Significantly changed benchmark results (PR vs master)

Significantly changed benchmark results (master vs previous release)

Full benchmark results can be found as artifacts in GitHub Actions
(click on checks at the top of the PR).

Comment on lines 1837 to 1812
sol = Union(FiniteSet(Integer(0)), Intersection(S.Reals, FiniteSet(Pow(Add(Mul(Integer(-1),
Pow(x, Integer(2))), zoo), Rational(1, 2)), Mul(Integer(-1),
Pow(Add(Mul(Integer(-1), Pow(x, Integer(2))), zoo), Rational(1, 2))))))

# Should work, but will not. See #21943
assert solveset(expr, y, domain=S.Reals) == sol
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think evaluate=False just needs to be used somewhere.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is that the args are flipped in zoo - x**2.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that the cause of the problem is evaluate=False being used somewhere...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to rewrite the expressions some time ago, but couldn't figure out where to put the evaluate=False. May have another go, but indeed it would be better to only get one solution...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To recreate the output of solveset we need evaluate=False for the Add in zoo - x**2:

In [15]: sol = Union(FiniteSet(Integer(0)), Intersection(S.Reals, FiniteSet(Pow(Add(Mul(Integer(-1),
    ...:             Pow(x, Integer(2))), zoo, evaluate=False), Rational(1, 2)), Mul(Integer(-1),
    ...:             Pow(Add(Mul(Integer(-1), Pow(x, Integer(2))), zoo, evaluate=False), Rational(1, 2))))))
    ...: 
    ...: expr = -2*y*exp(-x**2 - y**2)*Abs(x)
    ...: 
    ...: print(solveset(expr, y, Reals) == sol)
True

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the 2-arg Mul:

In [6]: e = zoo - x**2

In [7]: e.args
Out[7]: 
⎛       2⎞
⎝zoo, -xIn [8]: e2 = -1 * e

In [9]: e2
Out[9]: 
 2      
x  + zoo

In [10]: e2.args
Out[10]: 
⎛ 2     ⎞
⎝x , zooIn [11]: e2 == e2.func(*e2.args)
Out[11]: False

It can be fixed with

diff --git a/sympy/core/mul.py b/sympy/core/mul.py
index 9933eb601a..960b5baf25 100644
--- a/sympy/core/mul.py
+++ b/sympy/core/mul.py
@@ -281,7 +281,7 @@ def flatten(cls, seq):
                         ar = a*r
                         if ar:
                             bargs.insert(0, ar)
-                        bargs = [Add._from_args(bargs)]
+                        bargs = [Add(*bargs)]
                         rv = bargs, [], None
             if rv:
                 return rv

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe _unevaluated_Add(*args) -- I read the issue but am not seeing enough context to understand the problem. Add._from_args is much faster than Add and since you are making this change in core it makes me nervous.

@oscargus
Copy link
Contributor Author

oscargus commented Sep 4, 2021

I added the evalute=False in the test with a comment about that they should ideally not be required.

Didn't want to change the core mul-code...

@oscargus
Copy link
Contributor Author

oscargus commented Sep 4, 2021

I have fixed the zoo problems in a slightly more general way than discussed in #22019.

Also fixed solving arg in a way that at least is correct, but may not always return "minimal" solutions. For example, solveset(arg(x) , x) returns

Intersection(ConditionSet(x, Eq(im(x), 0), Complexes), ConditionSet(x, re(x) > 0, Complexes))

where the first ConditionSet is identical to x being real.

On the other hand, solveset(arg(x-I) , x) now do not throw and error, but returns

Intersection(ConditionSet(x, Eq(im(x) - 1, 0), Complexes), ConditionSet(x, re(x) > 0, Complexes))

@oscargus oscargus changed the title Added tests for #10085, #10876, #12032, #13396, #13961, #14541, #14642 Fixed solveset and added tests for already solved issues Sep 4, 2021
# Some inverse functions evaluate to zoo, remove if present
fs = FiniteSet(S.ComplexInfinity)
if fs.is_subset(g_ys):
g_ys -= fs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this does fix things in a more general way. It removes zoo but loses the reason for removing zoo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the option is to check each and every inverse function that the input will not lead to zoo as you suggested. I cannot really see that keeps the reason better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that once the function class is identified it will in almost all cases call _invert_real again with an evaluated version of the inverse function. So rather than checking for every evaluation if the input may yield zoo, which is a bit of a hassle and cases will have to be added once identified, it seems more general to check if the input to (the next call of) _invert_real is zoo. The only possible way that this may not work is if there are some functions that map zoo back to reals in a "controlled" way (not just inverting again). It doesn't seem to be any of those cases in the tests so far.

I added a test when I wrote this code to illustrate the point:
assert solveset(atan(x**2 - y**2)-pi/2, y, S.Reals) == EmptySet() which currently returns Intersection({-sqrt(x**2 + zoo), sqrt(x**2 + zoo)}, Reals).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if invert_real only returned sets that are strictly contained in the domain of a function. There are other ways this can go wrong like if if the subset test is indeterminate then a zoo case is not removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if invert_real only returned sets that are strictly contained in the domain of a function

Since it has the domain argument, I agree. I think I fixed a problem where the domain was integers but it wasn't return only integers. Those are troublesome bugs because you assume that the routine can be counted on to respect the domain and that you won't have worry about this in the the calling routine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree in general. The only other way I might think of is

g_ys = Intersection(g_ys, S.Reals)

which will also cover other cases (however, I believe that all the inverse functions return real-valued results for real-valued input, except for those that returns zoo, but I may be wrong). I'm a bit worried that this could explode in certain cases. (Intersection(ImageSet(..., Intersection(ImageSet(..., Intersection(...)), S.Reals), S.Reals)) Also, I am not sure how computationally efficient this is, but can give it a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will also be interesting see if that deals with: solveset(I*pi-log(x), x, S.Reals), which, if I guess correctly, will have an imaginary intermediate value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to fix this at the intersection-level in #22045.

@oscargus oscargus added this to the SymPy 1.9 milestone Sep 5, 2021
Co-authored-by: Christopher Smith <smichr@gmail.com>
@smichr smichr enabled auto-merge September 7, 2021 19:56
@smichr smichr merged commit 0c90f09 into sympy:master Sep 7, 2021
Comment on lines +1124 to +1126
new_res = Intersection(result, domain)
# Check if the domain is really contributing
if new_res.is_subset(result):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this always True? (Or rather is it not safe to assume that it is always True?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be that you are correct.

The thing was that just adding the domain sometimes led to some bad looking results. Typically, the domain was not really required in some cases and still remained. I do not remember an exact example, but, say, Intersection(ConditionSet(... S.Reals), S.Reals) or something similar. This was one of many attempts to get rid of those that at least didn't add anything "stupid".

Maybe it would be better to just add the domain unless it is S.Complexes and use that as input for improving Intersection?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, forgot what I had already written. I'll try to find a better way of doing this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the problem:
solveset(Abs(x) - n, x, S.Reals) currently returns ConditionSet(x, Contains(n, Interval(0, oo)), {-n, n}), by Intersecting with the Reals domain we get ConditionSet(x, Contains(_n, Interval(0, oo)), Intersection({-_n, _n}, Reals)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize that I was trying to solve issues that I didn't fully understood. So I'll just revert this part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment