Repository: sympy/sympy

Issue:
don't use ConditionSet as a container for unsolved sets of equations
We should probably have a different way to represents sets of unsolved equations. An existing test shows how ConditionSet is being used for this purpose:

```
def test_nonlinsolve_conditionset():
    # when solveset failed to solve all the eq
    # return conditionset
    f = Function('f')
    f1 = f(x) - pi/2
    f2 = f(y) - 3*pi/2
    intermediate_system = FiniteSet(2*f(x) - 3*pi, 2*f(y) - pi)
    symbols = FiniteSet(x, y)
    soln = ConditionSet(
        symbols,
        intermediate_system,
        S.Complexes)
    assert nonlinsolve([f1, f2], [x,y]) == soln
```

See also #14500

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v149). 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.

Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it. <details><summary>Click here to see the pull request description that was parsed.</summary>

    <!-- 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://github.com/blog/1506-closing-issues-via-pull-requests . Please also
    write a comment on that issue linking back to this pull request once it is
    open. -->
    Fixes #14523

    #### Brief description of what is fixed or changed

    `FiniteSet` for the `condition` parameter is not proper and should be replaced like `And(Eq(lhs, 0) ...)`,
    and the construction with `Tuple` for `syms` should also be refined.

    I also think that most of the work can be made simpler with `sympify` here, rather than trying to build complicated handlers like `isinstance(x, (tuple, Tuple))`.

    I'm only testing out the possible test failings at this first draft, and I may be able to come up with some better ideas of how to make some old behaviors go through deprecation cycles, or such.

    #### Other comments


    #### Release Notes

    <!-- Write the release notes for this release below. 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 -->
    NO ENTRY
    <!-- END RELEASE NOTES -->


</details><p>

codecov[bot]: # [Codecov](https://codecov.io/gh/sympy/sympy/pull/17651?src=pr&el=h1) Report
> Merging [#17651](https://codecov.io/gh/sympy/sympy/pull/17651?src=pr&el=desc) into [master](https://codecov.io/gh/sympy/sympy/commit/5b8ea7b1e775848ac2ae6b82befb30d04406d128?src=pr&el=desc) will **decrease** coverage by `12.794%`.
> The diff coverage is `91.666%`.

```diff
@@              Coverage Diff               @@
##            master    #17651        +/-   ##
==============================================
- Coverage   74.777%   61.983%   -12.795%     
==============================================
  Files          634       634                
  Lines       165327    165324         -3     
  Branches     38857     38857                
==============================================
- Hits        123627    102473     -21154     
- Misses       36290     57015     +20725     
- Partials      5410      5836       +426
```


Patch:
diff --git a/sympy/sets/conditionset.py b/sympy/sets/conditionset.py
index 6437f471a8..b24a60f6f7 100644
--- a/sympy/sets/conditionset.py
+++ b/sympy/sets/conditionset.py
@@ -6,12 +6,16 @@
 from sympy.core.expr import Expr
 from sympy.core.function import Lambda
 from sympy.core.logic import fuzzy_bool
+from sympy.core.relational import Eq
 from sympy.core.symbol import Symbol, Dummy
+from sympy.core.sympify import _sympify
 from sympy.logic.boolalg import And, as_Boolean
-from sympy.sets.contains import Contains
-from sympy.sets.sets import Set, EmptySet, Union, FiniteSet
 from sympy.utilities.iterables import sift
 from sympy.utilities.misc import filldedent
+from sympy.utilities.exceptions import SymPyDeprecationWarning
+
+from .contains import Contains
+from .sets import Set, EmptySet, Union, FiniteSet, ProductSet, UniversalSet
 
 
 class ConditionSet(Set):
@@ -114,31 +118,46 @@ def __new__(cls, sym, condition, base_set=S.UniversalSet):
         # nonlinsolve uses ConditionSet to return an unsolved system
         # of equations (see _return_conditionset in solveset) so until
         # that is changed we do minimal checking of the args
-        if isinstance(sym, (Tuple, tuple)):  # unsolved eqns syntax
-            sym = Tuple(*sym)
-            condition = FiniteSet(*condition)
-            return Basic.__new__(cls, sym, condition, base_set)
+        sym = _sympify(sym)
+        base_set = _sympify(base_set)
+        condition = _sympify(condition)
+
+        if isinstance(condition, FiniteSet):
+            condition_orig = condition
+            temp = (Eq(lhs, 0) for lhs in condition)
+            condition = And(*temp)
+            SymPyDeprecationWarning(
+                feature="Using {} for condition".format(condition_orig),
+                issue=17651,
+                deprecated_since_version='1.5',
+                useinstead="{} for condition".format(condition)
+                ).warn()
+
         condition = as_Boolean(condition)
-        if isinstance(base_set, set):
-            base_set = FiniteSet(*base_set)
-        elif not isinstance(base_set, Set):
+
+        if isinstance(sym, Tuple):  # unsolved eqns syntax
+            return Basic.__new__(cls, sym, condition, base_set)
+
+        if not isinstance(base_set, Set):
             raise TypeError('expecting set for base_set')
+
         if condition is S.false:
             return S.EmptySet
-        if condition is S.true:
+        elif condition is S.true:
             return base_set
         if isinstance(base_set, EmptySet):
             return base_set
+
         know = None
         if isinstance(base_set, FiniteSet):
             sifted = sift(
-                base_set, lambda _: fuzzy_bool(
-                    condition.subs(sym, _)))
+                base_set, lambda _: fuzzy_bool(condition.subs(sym, _)))
             if sifted[None]:
                 know = FiniteSet(*sifted[True])
                 base_set = FiniteSet(*sifted[None])
             else:
                 return FiniteSet(*sifted[True])
+
         if isinstance(base_set, cls):
             s, c, base_set = base_set.args
             if sym == s:
@@ -158,11 +177,13 @@ def __new__(cls, sym, condition, base_set=S.UniversalSet):
                     condition.xreplace({sym: dum}),
                     c.xreplace({s: dum}))
                 sym = dum
+
         if not isinstance(sym, Symbol):
             s = Dummy('lambda')
             if s not in condition.xreplace({sym: s}).free_symbols:
                 raise ValueError(
                     'non-symbol dummy not recognized in condition')
+
         rv = Basic.__new__(cls, sym, condition, base_set)
         return rv if know is None else Union(know, rv)
 
@@ -176,12 +197,9 @@ def free_symbols(self):
         return (c.free_symbols - s.free_symbols) | b.free_symbols
 
     def _contains(self, other):
-        d = Dummy()
-        try:
-            return self.as_relational(d).subs(d, other)
-        except TypeError:
-            # couldn't do the substitution without error
-            return False
+        return And(
+            Contains(other, self.base_set),
+            Lambda(self.sym, self.condition)(other))
 
     def as_relational(self, other):
         return And(Lambda(self.sym, self.condition)(
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 61b7388806..e1927447c3 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -2530,10 +2530,9 @@ def linsolve(system, *symbols):
 
 def _return_conditionset(eqs, symbols):
         # return conditionset
+        eqs = (Eq(lhs, 0) for lhs in eqs)
         condition_set = ConditionSet(
-            Tuple(*symbols),
-            FiniteSet(*eqs),
-            S.Complexes)
+            Tuple(*symbols), And(*eqs), S.Complexes**len(symbols))
         return condition_set
 
 


Test patch:
diff --git a/sympy/printing/tests/test_latex.py b/sympy/printing/tests/test_latex.py
index 868dfcbfba..f52752ea17 100644
--- a/sympy/printing/tests/test_latex.py
+++ b/sympy/printing/tests/test_latex.py
@@ -2345,11 +2345,12 @@ def test_issue_15353():
     from sympy import ConditionSet, Tuple, FiniteSet, S, sin, cos
     a, x = symbols('a x')
     # Obtained from nonlinsolve([(sin(a*x)),cos(a*x)],[x,a])
-    sol = ConditionSet(Tuple(x, a), FiniteSet(sin(a*x), cos(a*x)), S.Complexes)
+    sol = ConditionSet(
+        Tuple(x, a), Eq(sin(a*x), 0) & Eq(cos(a*x), 0), S.Complexes**2)
     assert latex(sol) == \
-        r'\left\{\left( x, \  a\right) \mid \left( x, \  a\right) \in '\
-        r'\mathbb{C} \wedge \left\{\sin{\left(a x \right)}, \cos{\left(a x '\
-        r'\right)}\right\} \right\}'
+        r'\left\{\left( x, \  a\right) \mid \left( x, \  a\right) \in ' \
+        r'\mathbb{C}^{2} \wedge \sin{\left(a x \right)} = 0 \wedge ' \
+        r'\cos{\left(a x \right)} = 0 \right\}'
 
 
 def test_trace():
diff --git a/sympy/sets/tests/test_conditionset.py b/sympy/sets/tests/test_conditionset.py
index 8a4d2d0de7..95983f2c24 100644
--- a/sympy/sets/tests/test_conditionset.py
+++ b/sympy/sets/tests/test_conditionset.py
@@ -1,8 +1,8 @@
 from sympy.sets import (ConditionSet, Intersection, FiniteSet,
-    EmptySet, Union)
+    EmptySet, Union, Contains)
 from sympy import (Symbol, Eq, S, Abs, sin, pi, Interval,
     And, Mod, oo, Function)
-from sympy.utilities.pytest import raises
+from sympy.utilities.pytest import raises, XFAIL, warns_deprecated_sympy
 
 
 w = Symbol('w')
@@ -131,8 +131,11 @@ def test_subs_CondSet():
 
 
 def test_subs_CondSet_tebr():
-    # to eventually be removed
-    c = ConditionSet((x, y), {x + 1, x + y}, S.Reals)
+    with warns_deprecated_sympy():
+        assert ConditionSet((x, y), {x + 1, x + y}, S.Reals) == \
+            ConditionSet((x, y), Eq(x + 1, 0) & Eq(x + y, 0), S.Reals)
+
+    c = ConditionSet((x, y), Eq(x + 1, 0) & Eq(x + y, 0), S.Reals)
     assert c.subs(x, z) == c
 
 
@@ -145,10 +148,9 @@ def test_dummy_eq():
     assert c.dummy_eq(C(x, x < 1, S.Reals)) == False
     raises(ValueError, lambda: c.dummy_eq(C(x, x < 1, S.Reals), z))
 
-    # to eventually be removed
-    c1 = ConditionSet((x, y), {x + 1, x + y}, S.Reals)
-    c2 = ConditionSet((x, y), {x + 1, x + y}, S.Reals)
-    c3 = ConditionSet((x, y), {x + 1, x + y}, S.Complexes)
+    c1 = ConditionSet((x, y), Eq(x + 1, 0) & Eq(x + y, 0), S.Reals)
+    c2 = ConditionSet((x, y), Eq(x + 1, 0) & Eq(x + y, 0), S.Reals)
+    c3 = ConditionSet((x, y), Eq(x + 1, 0) & Eq(x + y, 0), S.Complexes)
     assert c1.dummy_eq(c2)
     assert c1.dummy_eq(c3) is False
     assert c.dummy_eq(c1) is False
@@ -167,5 +169,13 @@ def test_contains():
     assert ConditionSet(x, y > 5, Interval(1, 7)
         ).contains(8) is S.false
     assert ConditionSet(x, y > 5, Interval(1, 7)
-        ).contains(w) == And(S.One <= w, w <= 7, y > 5)
-    assert 0 not in ConditionSet(x, 1/x >= 0, S.Reals)
+        ).contains(w) == And(Contains(w, Interval(1, 7)), y > 5)
+
+@XFAIL
+def test_failing_contains():
+    # XXX This may have to return unevaluated Contains object
+    # because 1/0 should not be defined for 1 and 0 in the context of
+    # reals, but there is a nonsensical evaluation to ComplexInfinity
+    # and the comparison is giving an error.
+    assert ConditionSet(x, 1/x >= 0, S.Reals).contains(0) == \
+        Contains(0, ConditionSet(x, 1/x >= 0, S.Reals), evaluate=False)
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index 0c72ca08a1..bf26855c42 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -1512,12 +1512,12 @@ def test_nonlinsolve_conditionset():
     f = Function('f')
     f1 = f(x) - pi/2
     f2 = f(y) - pi*Rational(3, 2)
-    intermediate_system = FiniteSet(2*f(x) - pi, 2*f(y) - 3*pi)
+    intermediate_system = Eq(2*f(x) - pi, 0) & Eq(2*f(y) - 3*pi, 0)
     symbols = Tuple(x, y)
     soln = ConditionSet(
         symbols,
         intermediate_system,
-        S.Complexes)
+        S.Complexes**2)
     assert nonlinsolve([f1, f2], [x, y]) == soln