Repository: sympy/sympy

Issue:
KeyError in Nonlinsolve
```
>>> nonlinsolve([x*sin(45) - y*cos(q), x*cos(45) - y*sin(q), 9*x*sin(45)/10 + y*cos(q), 9*x*cos(45)/10 + y*sin(z) - z], x, y, z, q)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'q' is not defined
>>> q = Symbol('q')
>>> nonlinsolve([x*sin(45) - y*cos(q), x*cos(45) - y*sin(q), 9*x*sin(45)/10 + y*cos(q), 9*x*cos(45)/10 + y*sin(z) - z], x, y, z, q)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/base/data/home/apps/s~sympy-live-hrd/57.417397408462217066/sympy/sympy/solvers/solveset.py", line 3063, in nonlinsolve
    polys_expr + nonpolys, symbols, exclude=denominators)
  File "/base/data/home/apps/s~sympy-live-hrd/57.417397408462217066/sympy/sympy/solvers/solveset.py", line 2719, in substitution
    old_result, solveset_real)
  File "/base/data/home/apps/s~sympy-live-hrd/57.417397408462217066/sympy/sympy/solvers/solveset.py", line 2696, in _solve_using_known_values
    imgst = soln_imageset[sol]
KeyError: 0
```

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v161). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



* No release notes entry will be added for this pull request.

<details><summary>Click here to see the pull request description that was parsed.</summary>

    In `nonlinsolve` KeyError is fixed.

    This error was arising since the non Imageset element was not fixed. And also the `ConditionSet` now also contains a solution in itself.

    Closes #17933
    Closes #21022
    <!-- BEGIN RELEASE NOTES -->
    NO ENTRY
    <!-- END RELEASE NOTES -->

</details><p>

smichr: Is this right? You are replacing `sol` with the domain of the `ConditionSet`? The assumptions about what is happening and why it will work have to be made transparent.
MaqOwais: > Is this right? You are replacing sol with the domain of the ConditionSet? 


This is actually for the cases where `conditionset` also contains the result (at `ConditionSet(...).args[2]`). (See below)

https://github.com/sympy/sympy/blob/f4c2dce477141ac1b79fea79ed71692988bbb4bf/sympy/solvers/tests/test_solveset.py#L979-L1008



Patch:
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 869625adbd..eb77ef03b4 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -3014,33 +3014,24 @@ def add_intersection_complement(result, intersection_dict, complement_dict):
     # end of def add_intersection_complement()
 
     def _extract_main_soln(sym, sol, soln_imageset):
-        """Separate the Complements, Intersections, ImageSet lambda expr
-        and its base_set.
+        """Separate the Complements, Intersections, ImageSet lambda expr and
+        its base_set. This function returns the unmasks sol from different classes
+        of sets and also returns the appended ImageSet elements in a
+        soln_imageset (dict: where key as unmasked element and value as ImageSet).
         """
         # if there is union, then need to check
         # Complement, Intersection, Imageset.
         # Order should not be changed.
+        if isinstance(sol, ConditionSet):
+            # extracts any solution in ConditionSet
+            sol = sol.base_set
+
         if isinstance(sol, Complement):
             # extract solution and complement
             complements[sym] = sol.args[1]
             sol = sol.args[0]
             # complement will be added at the end
             # using `add_intersection_complement` method
-        if isinstance(sol, Intersection):
-            # Interval/Set will be at 0th index always
-            if sol.args[0] not in (S.Reals, S.Complexes):
-                # Sometimes solveset returns soln with intersection
-                # S.Reals or S.Complexes. We don't consider that
-                # intersection.
-                intersections[sym] = sol.args[0]
-            sol = sol.args[1]
-        # after intersection and complement Imageset should
-        # be checked.
-        if isinstance(sol, ImageSet):
-            soln_imagest = sol
-            expr2 = sol.lamda.expr
-            sol = FiniteSet(expr2)
-            soln_imageset[expr2] = soln_imagest
 
         # if there is union of Imageset or other in soln.
         # no testcase is written for this if block
@@ -3057,6 +3048,22 @@ def _extract_main_soln(sym, sol, soln_imageset):
                     # append them directly
                     sol += FiniteSet(sol_arg2)
 
+        if isinstance(sol, Intersection):
+            # Interval/Set will be at 0th index always
+            if sol.args[0] not in (S.Reals, S.Complexes):
+                # Sometimes solveset returns soln with intersection
+                # S.Reals or S.Complexes. We don't consider that
+                # intersection.
+                intersections[sym] = sol.args[0]
+            sol = sol.args[1]
+        # after intersection and complement Imageset should
+        # be checked.
+        if isinstance(sol, ImageSet):
+            soln_imagest = sol
+            expr2 = sol.lamda.expr
+            sol = FiniteSet(expr2)
+            soln_imageset[expr2] = soln_imagest
+
         if not isinstance(sol, FiniteSet):
             sol = FiniteSet(sol)
         return sol, soln_imageset
@@ -3149,7 +3156,7 @@ def _append_new_soln(rnew, sym, sol, imgset_yes, soln_imageset,
             elif eq is not None:
                 newresult, rnew, delete_soln = _append_eq(
                     eq, newresult, rnew, delete_soln)
-            elif soln_imageset:
+            elif sol in soln_imageset.keys():
                 rnew[sym] = soln_imageset[sol]
                 # restore original imageset
                 _restore_imgset(rnew, original_imageset, newresult)
@@ -3233,6 +3240,15 @@ def _solve_using_known_values(result, solver):
                         soln = solver(eq2, sym)
                         total_solvest_call += 1
                         soln_new = S.EmptySet
+                        if isinstance(soln, ConditionSet):
+                            if soln.base_set in (S.Reals, S.Complexes):
+                                soln = S.EmptySet
+                                # don't do `continue` we may get soln
+                                # in terms of other symbol(s)
+                                not_solvable = True
+                                total_conditionst += 1
+                            else:
+                                soln = soln.base_set
                         if isinstance(soln, Complement):
                             # separate solution and complement
                             complements[sym] = soln.args[1]
@@ -3256,12 +3272,6 @@ def _solve_using_known_values(result, solver):
                         # If sovleset is not able to solve equation `eq2`. Next
                         # time we may get soln using next equation `eq2`
                         continue
-                    if isinstance(soln, ConditionSet):
-                            soln = S.EmptySet
-                            # don't do `continue` we may get soln
-                            # in terms of other symbol(s)
-                            not_solvable = True
-                            total_conditionst += 1
 
                     if soln is not S.EmptySet:
                         soln, soln_imageset = _extract_main_soln(
@@ -3290,7 +3300,7 @@ def _solve_using_known_values(result, solver):
                                 # Then subs known value
                                 rnew[k] = v.subs(sym, sol)
                         # and add this new solution
-                        if soln_imageset:
+                        if sol in soln_imageset.keys():
                             # replace all lambda variables with 0.
                             imgst = soln_imageset[sol]
                             rnew[sym] = imgst.lamda(
@@ -3328,8 +3338,19 @@ def _solve_using_known_values(result, solver):
     if total_conditionset == total_solveset_call and total_solveset_call != -1:
         return _return_conditionset(eqs_in_better_order, all_symbols)
 
+    # don't keep duplicate solutions
+    filtered_complex = []
+    for i in list(new_result_complex):
+        for j in list(new_result_real):
+            if i.keys() != j.keys():
+                continue
+            if all(a.dummy_eq(b) for a, b in zip(i.values(), j.values()) \
+                if type(a) != int or type(b) != int):
+                break
+        else:
+            filtered_complex.append(i)
     # overall result
-    result = new_result_real + new_result_complex
+    result = new_result_real + filtered_complex
 
     result_all_variables = []
     result_infinite = []


Test patch:
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index 14502703c2..4cf0c2b312 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -1563,7 +1563,7 @@ def test_nonlinsolve_basic():
 
 
 def test_nonlinsolve_abs():
-    soln = FiniteSet((x, Abs(x)))
+    soln = FiniteSet((y, y), (-y, y))
     assert nonlinsolve([Abs(x) - y], x, y) == soln
 
 
@@ -1713,11 +1713,8 @@ def test_issue_19050():
 
 def test_issue_16618():
     # AttributeError is removed !
-    # have to remove the redundancy
-    _n = Dummy('n')
     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),\
-        (x, 2*_n*pi), (2*_n*pi, y), (x, 2*_n*pi + pi), (2*_n*pi + pi, y))
+    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)):
@@ -1912,6 +1909,62 @@ def test_raises_substitution():
     raises(TypeError, lambda: substitution([x**2 -1], x))
     raises(TypeError, lambda: substitution([x**2 -1], 1))
 
+
+def test_issue_21022():
+    from sympy.core.sympify import sympify
+
+    eqs = [
+    'k-16',
+    'p-8',
+    'y*y+z*z-x*x',
+    'd - x + p',
+    'd*d+k*k-y*y',
+    'z*z-p*p-k*k',
+    'abc-efg',
+    ]
+    efg = Symbol('efg')
+    eqs = [sympify(x) for x in eqs]
+
+    syb = list(ordered(set.union(*[x.free_symbols for x in eqs])))
+    res = nonlinsolve(eqs, syb)
+
+    ans = FiniteSet(
+    (efg, sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16),
+    efg, 16, 8, 8 + sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16),
+    sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16, -8*sqrt(5)),
+    (efg, sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16),
+    efg, 16, 8, 8 + sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16),
+    sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16, 8*sqrt(5)),
+    (efg, -sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16),
+    efg, 16, 8, -sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16) + 8,
+    sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16, -8*sqrt(5)),
+    (efg, -sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16),
+    efg, 16, 8, -sqrt(-16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16)*sqrt(16 + sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16) + 8,
+    sqrt(640 - 128*sqrt(5))*sqrt(128*sqrt(5) + 640)/16, 8*sqrt(5))
+    )
+
+    assert len(res) == len(ans) == 4
+    assert res == ans
+    for result in res.args:
+        assert len(result) == 8
+
+
+def test_issue_17933():
+    eq1 = x*sin(45) - y*cos(q)
+    eq2 = x*cos(45) - y*sin(q)
+    eq3 = 9*x*sin(45)/10 + y*cos(q)
+    eq4 = 9*x*cos(45)/10 + y*sin(z) - z
+
+    assert nonlinsolve([eq1, eq2, eq3, eq4], x, y, z, q) ==\
+        FiniteSet((0, 0, 0, q))
+
+
+def test_issue_14565():
+    # removed redundancy
+    assert dumeq(nonlinsolve([k + m, k + m*exp(-2*pi*k)], [k, m]) ,
+        FiniteSet((-n*I, ImageSet(Lambda(n, n*I), S.Integers))))
+
+
 # end of tests for nonlinsolve
 
 
@@ -2599,9 +2652,9 @@ def test_issue_17276():
 
 
 def test_issue_10426():
-    x=Dummy('x')
-    a=Symbol('a')
-    n=Dummy('n')
+    x = Dummy('x')
+    a = Symbol('a')
+    n = Dummy('n')
     assert (solveset(sin(x + a) - sin(x), a)).dummy_eq(Dummy('x')) == (Union(
         ImageSet(Lambda(n, 2*n*pi), S.Integers),
         Intersection(S.Complexes, ImageSet(Lambda(n, -I*(I*(2*n*pi + arg(-exp(-2*I*x))) + 2*im(x))),