-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix(solvers): ImageSet Union simplify (#18489 improved a bit) #21196
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: Oscar Benjamin <oscar.j.benjamin@gmail.com>
✅ 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.
Click here to see the pull request description that was parsed.
|
a262beb
to
41df7ed
Compare
You should keep the original commits intact when reviving a PR so that they still show as being from the original author. The reason #18489 did not get merged was because this would be better handled in simplification rather than in the evaluation code. The handler system for union is inherently limited to pairwise unions so implementing this there will never handle all cases. Also there is already too much computation going on in the evaluation of unions etc. This should be handled in a simplify function and should not be something that happens automatically when doing |
Can you give an example of this? I think it handles almost all cases.
So, this should be copy-pasted in simplify function? |
As long as you don't close this branch you can recreate the img_union branch with the original commits intact and then force push here and it will update. |
Big headache, but I think I have the original PR along with a single commit of yours which incorporates all of the changes here while retaining original commits. If you agree you can clone my iseta branch and replace your img_union branch with it and then force push here to update. Although there was some concern (@oscarbenjamin ) about the simplification of imageset on integers happening automatically instead of through simplification, it seems reasonable to have this be automatic to me (and the routine is conservative in that it only makes the change if both imagesets involve the integer domain). |
But what is the need for it? Is it in this way? (from img_union)
I do think the same. ( since it only calls this which I think it doesn't matter a lot ) sympy/sympy/sets/handlers/union.py Lines 145 to 146 in 4793b42
|
I think like this:
|
As mention above, "[y]ou should keep the original commits intact when reviving a PR" |
Update union.py Update union.py Update union.py
That command is to create, but not move to, a backup branch from the current branch at that point. I m not sure why it is not recognizing the directory. Perhaps try
Also, a problem with this PR is that there is a regression in a test: master:
>>> nonlinsolve([sin(x) - 1, cos(x)], x)
FiniteSet((ImageSet(Lambda(_n, 2*_n*pi + pi/2), Integers),))
this:
>>> nonlinsolve([sin(x) - 1, cos(x)], x)
FiniteSet((ImageSet(Lambda(_n, _n*pi + pi/2), Integers),)) |
I think this is what needed? |
Are you refering to the |
Nope, I was referring to the git changes. |
Oh...you got it to update. Now it looks like there are some tests failing. Nevermind on previous comment :-) |
I think I've to update the tests locally according to these updates, isn't it? |
if cancel((s - q)/p).is_positive: | ||
# to maintain order so the tests shouldn't fail | ||
return union_sets(b, a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like you to comment on this block.
There is far too much automatic evaluation in sympy and it is problematic for several reasons. It makes sympy slow because the evaluation code runs every time any manipulation is performed (e.g. subs, xreplace, etc). The other reason is that it makes it hard to represent expressions in particular ways when wanted because they keep evaluating into something else. The use of In [20]: S1 = ImageSet(Lambda(n, 2*pi*n+x), Integers)
In [21]: S2 = ImageSet(Lambda(n, 2*pi*n+x+pi), Integers)
In [22]: S3 = Union(S1, S2, evaluate=False)
In [23]: S3
Out[23]: {2⋅π⋅n + x │ n ∊ ℤ} ∪ {2⋅π⋅n + x + π │ n ∊ ℤ}
In [24]: S3.subs(x, y)
Out[24]: {n⋅π + y + π │ n ∊ ℤ} In general I think that we should not add more computation in evaluation. This is particularly problematic in sets because there are not many other routines defined for manipulating sets so every time more is added in evaluation we encourage future code to go there as well. In this particular case there are strong reasons for not using this approach. The current mechanism of In [1]: for N in [2, 4, 8, 16]: # master
...: sets = [ImageSet(Lambda(n, (N+1)*pi*n + m), Integers) for m in range(N)]
...: %time ok = Union(*sets)
...:
CPU times: user 1.52 ms, sys: 39 µs, total: 1.56 ms
Wall time: 1.56 ms
CPU times: user 3.04 ms, sys: 14 µs, total: 3.05 ms
Wall time: 3.06 ms
CPU times: user 6.57 ms, sys: 36 µs, total: 6.61 ms
Wall time: 6.62 ms
CPU times: user 12.8 ms, sys: 43 µs, total: 12.8 ms
Wall time: 12.8 ms Now look at the PR: In [1]: for N in [2, 4, 8, 16]: # PR
...: sets = [ImageSet(Lambda(n, (N+1)*pi*n + m), Integers) for m in range(N)]
...: %time ok = Union(*sets)
...:
CPU times: user 20.2 ms, sys: 361 µs, total: 20.5 ms
Wall time: 20.7 ms
CPU times: user 64.6 ms, sys: 338 µs, total: 65 ms
Wall time: 65.2 ms
CPU times: user 232 ms, sys: 852 µs, total: 233 ms
Wall time: 233 ms
CPU times: user 1.01 s, sys: 9.65 ms, total: 1.02 s
Wall time: 1.03 s For
SymPy has tried to work around this with caching but it's not a substitute for having good algorithmic complexity. A Finally using only pairwise comparisons like this will inherently fail in any situation where the number of sets to be merged is not a power of 2:
It isn't possible to merge any two of these pairs and the algorithm does not look beyond pairwise comparison. The approach taken in this PR is fundamentally unable to achieve the kind of simplification that is needed for the output of |
How about this: diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index 423a991e3d..0550142b21 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -1009,11 +1009,12 @@ def test_solve_invalid_sol():
def test_solve_trig_simplified():
- assert solveset_real(sin(x), x) == ImageSet(Lambda(n, n*pi), S.Integers)
- assert solveset_real(cos(x), x) == \
- ImageSet(Lambda(n, n*pi + pi/2), S.Integers)
- assert solveset_real(cos(x) + sin(x), x) == \
- ImageSet(Lambda(n, n*pi + 3*pi/4), S.Integers)
+ assert dumeq(solveset_real(sin(x), x),
+ ImageSet(Lambda(n, n*pi), S.Integers))
+ assert dumeq(solveset_real(cos(x), x),
+ ImageSet(Lambda(n, n*pi + pi/2), S.Integers))
+ assert dumeq(solveset_real(cos(x) + sin(x), x),
+ ImageSet(Lambda(n, n*pi + 3*pi/4), S.Integers))
@XFAIL
@@ -1674,23 +1675,21 @@ def test_solve_nonlinear_trans():
def test_issue_19050():
# test_issue_19050 --> TypeError removed
assert dumeq(nonlinsolve([x + y, sin(y)], [x, y]),
- FiniteSet((ImageSet(Lambda(n, -2*n*pi), S.Integers), ImageSet(Lambda(n, 2*n*pi), S.Integers)),\
- (ImageSet(Lambda(n, -2*n*pi - pi), S.Integers), ImageSet(Lambda(n, 2*n*pi + pi), S.Integers))))
+ FiniteSet((ImageSet(Lambda(n, -n*pi), S.Integers),
+ ImageSet(Lambda(n, n*pi), S.Integers))))
assert dumeq(nonlinsolve([x + y, sin(y) + cos(y)], [x, y]),
- FiniteSet((ImageSet(Lambda(n, -2*n*pi - 3*pi/4), S.Integers), ImageSet(Lambda(n, 2*n*pi + 3*pi/4), S.Integers)), \
- (ImageSet(Lambda(n, -2*n*pi - 7*pi/4), S.Integers), ImageSet(Lambda(n, 2*n*pi + 7*pi/4), S.Integers))))
+ FiniteSet((ImageSet(Lambda(n, -n*pi - 3*pi/4), S.Integers),
+ ImageSet(Lambda(n, n*pi + 3*pi/4), S.Integers))))
def test_issue_16618():
- # AttributeError is removed !
- eqn = [sin(x)*sin(y), cos(x)*cos(y) - 1]
- ans = FiniteSet((x, 2*n*pi), (2*n*pi, y), (x, 2*n*pi + pi), (2*n*pi + pi, y))
- sol = nonlinsolve(eqn, [x, y])
-
- for i0, j0 in zip(ordered(sol), ordered(ans)):
- assert len(i0) == len(j0) == 2
- assert all(a.dummy_eq(b) for a, b in zip(i0, j0))
+ sol = nonlinsolve([sin(x)*sin(y), cos(x)*cos(y) - 1], [x, y])
+ ans = [(x, n*pi), (n*pi, y)]
assert len(sol) == len(ans)
+ for i, j in zip(ordered(sol), ordered(ans)):
+ assert len(i) == len(j)
+ for a, b in zip(i, j):
+ assert a.dummy_eq(b)
def test_issue_5132_1():
|
Excellent answer by @oscarbenjamin here makes the point clear. We cannot add this into the union handler. This will have to be a simplification method or the solvers themselves will have to be more careful about not injecting all solutions like this and then requiring simplification to remove them. This is a good principle: limit complexity at the source. |
#21196 (comment) |
After reading this #21196 (comment) we can conclude that a new approach should be built and that should be based on these 2 points...
|
I prefer to base it on the principle that the actor putting in the redundant solutions should be more careful. |
I agree but it would still also be good to be able to simplify a union of imagesets. |
Yes, I think the combination of these 2 ideas will do wonders! |
# TODO: add more simple testcases when solveset returns | ||
# simplified soln for Trig eq | ||
|
||
# fails because it gives Lambda(n, n*pi + pi/2) and | ||
# 3*pi/2 does not satisfy first equation (giving -2 instead of 0) | ||
assert nonlinsolve([sin(x) - 1, cos(x) -1 ], x) == S.EmptySet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line can be moved out of the failing tests, I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, more important is to make _solve_trig
smarter, afterward making the modifications mentioned above( #21196 (comment)) than afterward these comments can be removed.
I think it's a bit long journey, so IMO let's concentrate on another simpler PRs, if you don't mind : )
Definitely, I'll try to come back to this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Simplification routines can be a rabbit's trail.
References to other Issues or PRs
Closes #17838
Closes #11188
re-uses tests from #17838 / #17079 / #18489 (with attribution)
Brief description of what is fixed or changed
This PR adds a union handler for Integer-based ImageSets such as those often returned by solveset for trigonometric equations.
Before (i.e. with master):
After:
(example borrowed from #11188, thanks @Shekharrajak )
Other comments
The union multi dispatch code will only consider pairwise unions, so not all possible simplifications will be recognized. But the improvements are still obvious.
TODO: add some more tests.
Release Notes
sets
I have just tried to understand #18489 and improved a bit (the changes are no too large, also the description is the same). Further, improvements are welcome to discuss here.
CREDITS : @ gschintgen , @oscarbenjamin , @ Shekharrajak
NO ENTRY