Repository: sympy/sympy

Issue:
Use ConditionSet as an unsolved object for solveset
In https://github.com/sympy/sympy/pull/9696 we implemented a `ConditionSet` object which can be used to represent unsolved equations and inequalities as a Set object. We need to use it as an unsolved object in solveset.

Discussion:

Patch:
diff --git a/sympy/sets/conditionset.py b/sympy/sets/conditionset.py
index c1ce9993f6..c3627b9486 100644
--- a/sympy/sets/conditionset.py
+++ b/sympy/sets/conditionset.py
@@ -33,5 +33,10 @@ def __new__(cls, condition, base_set):
     condition = property(lambda self: self.args[0])
     base_set = property(lambda self: self.args[1])
 
+    def _intersect(self, other):
+        if not isinstance(other, ConditionSet):
+            return ConditionSet(self.condition,
+                                Intersection(self.base_set, other))
+
     def contains(self, other):
         return And(self.condition(other), self.base_set.contains(other))
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 05fe51b83c..ce0b8dc70c 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -18,7 +18,7 @@
 from sympy.functions.elementary.trigonometric import (TrigonometricFunction,
                                                       HyperbolicFunction)
 from sympy.sets import (FiniteSet, EmptySet, imageset, Interval, Intersection,
-                        Union)
+                        Union, ConditionSet)
 from sympy.matrices import Matrix
 from sympy.polys import (roots, Poly, degree, together, PolynomialError,
                          RootOf)
@@ -355,6 +355,8 @@ def solveset_real(f, symbol):
     Set
         A set of values for `symbol` for which `f` is equal to
         zero. An `EmptySet` is returned if no solution is found.
+        A `ConditionSet` is returned as unsolved object if algorithms
+        to evaluate complete solutions are not yet implemented.
 
     `solveset_real` claims to be complete in the set of the solution it
     returns.
@@ -363,7 +365,7 @@ def solveset_real(f, symbol):
     ======
 
     NotImplementedError
-        The algorithms for to find the solution of the given equation are
+        Algorithms to solve inequalities in complex domain are
         not yet implemented.
     ValueError
         The input is not valid.
@@ -464,7 +466,7 @@ def solveset_real(f, symbol):
                 else:
                     result += solveset_real(equation, symbol)
         else:
-            raise NotImplementedError
+            result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Reals)
 
     if isinstance(result, FiniteSet):
         result = [s for s in result
@@ -497,7 +499,7 @@ def _solve_real_trig(f, symbol):
     g, h = g.expand(), h.expand()
     g, h = g.subs(exp(I*symbol), y), h.subs(exp(I*symbol), y)
     if g.has(symbol) or h.has(symbol):
-        raise NotImplementedError
+        return ConditionSet(Lambda(symbol, Eq(f, 0)), S.Reals)
 
     solns = solveset_complex(g, y) - solveset_complex(h, y)
 
@@ -507,7 +509,7 @@ def _solve_real_trig(f, symbol):
     elif solns is S.EmptySet:
         return S.EmptySet
     else:
-        raise NotImplementedError
+        return ConditionSet(Lambda(symbol, Eq(f, 0)), S.Reals)
 
 
 def _solve_as_poly(f, symbol, solveset_solver, invert_func):
@@ -529,12 +531,11 @@ def _solve_as_poly(f, symbol, solveset_solver, invert_func):
             if poly.degree() <= len(solns):
                 result = FiniteSet(*solns)
             else:
-                raise NotImplementedError("Couldn't find all roots "
-                                          "of the equation %s" % f)
+                result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
     else:
         poly = Poly(f)
         if poly is None:
-            raise NotImplementedError("Could not convert %s to Poly" % f)
+            result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
         gens = [g for g in poly.gens if g.has(symbol)]
 
         if len(gens) == 1:
@@ -546,8 +547,7 @@ def _solve_as_poly(f, symbol, solveset_solver, invert_func):
                                           quintics=True).keys())
 
             if len(poly_solns) < deg:
-                raise NotImplementedError("Couldn't find all the roots of "
-                                          "the equation %s" % f)
+                result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
 
             if gen != symbol:
                 y = Dummy('y')
@@ -555,11 +555,9 @@ def _solve_as_poly(f, symbol, solveset_solver, invert_func):
                 if lhs is symbol:
                     result = Union(*[rhs_s.subs(y, s) for s in poly_solns])
                 else:
-                    raise NotImplementedError(
-                        "inversion of %s not handled" % gen)
+                    result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
         else:
-            raise NotImplementedError("multiple generators not handled"
-                                      " by solveset")
+            result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
 
     if result is not None:
         if isinstance(result, FiniteSet):
@@ -573,7 +571,7 @@ def _solve_as_poly(f, symbol, solveset_solver, invert_func):
                 result = imageset(Lambda(s, expand_complex(s)), result)
         return result
     else:
-        raise NotImplementedError
+        return ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
 
 
 def _solve_as_poly_real(f, symbol):
@@ -672,7 +670,7 @@ def _solve_abs(f, symbol):
                                            symbol).intersect(q_neg_cond)
         return Union(sols_q_pos, sols_q_neg)
     else:
-        raise NotImplementedError
+        return ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
 
 
 def solveset_complex(f, symbol):
@@ -692,6 +690,8 @@ def solveset_complex(f, symbol):
     Set
         A set of values for `symbol` for which `f` equal to
         zero. An `EmptySet` is returned if no solution is found.
+        A `ConditionSet` is returned as an unsolved object if algorithms
+        to evaluate complete solutions are not yet implemented.
 
     `solveset_complex` claims to be complete in the solution set that
     it returns.
@@ -700,7 +700,7 @@ def solveset_complex(f, symbol):
     ======
 
     NotImplementedError
-        The algorithms for to find the solution of the given equation are
+        The algorithms to solve inequalities in complex domain  are
         not yet implemented.
     ValueError
         The input is not valid.
@@ -770,7 +770,7 @@ def solveset_complex(f, symbol):
                 else:
                     result += solveset_complex(equation, symbol)
         else:
-            raise NotImplementedError
+            result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
 
     if isinstance(result, FiniteSet):
         result = [s for s in result
@@ -800,6 +800,8 @@ def solveset(f, symbol=None, domain=S.Complexes):
     Set
         A set of values for `symbol` for which `f` is True or is equal to
         zero. An `EmptySet` is returned if no solution is found.
+        A `ConditionSet` is returned as unsolved object if algorithms
+        to evaluatee complete solution are not yet implemented.
 
     `solveset` claims to be complete in the solution set that it returns.
 
@@ -807,7 +809,7 @@ def solveset(f, symbol=None, domain=S.Complexes):
     ======
 
     NotImplementedError
-        The algorithms for to find the solution of the given equation are
+        The algorithms to solve inequalities in complex domain  are
         not yet implemented.
     ValueError
         The input is not valid.
@@ -889,8 +891,12 @@ def solveset(f, symbol=None, domain=S.Complexes):
             raise NotImplementedError("Inequalities in the complex domain are "
                                       "not supported. Try the real domain by"
                                       "setting domain=S.Reals")
-        return solve_univariate_inequality(
+        try:
+            result = solve_univariate_inequality(
             f, symbol, relational=False).intersection(domain)
+        except NotImplementedError:
+            result = ConditionSet(Lambda(symbol, f), domain)
+        return result
 
     if isinstance(f, (Expr, Number)):
         if domain is S.Reals:


Test patch:
diff --git a/sympy/sets/tests/test_conditionset.py b/sympy/sets/tests/test_conditionset.py
index 5786e91e3a..c7ba55c399 100644
--- a/sympy/sets/tests/test_conditionset.py
+++ b/sympy/sets/tests/test_conditionset.py
@@ -1,4 +1,4 @@
-from sympy.sets import ConditionSet
+from sympy.sets import (ConditionSet, Intersection)
 from sympy import (Symbol, Eq, S, sin, pi, Lambda, Interval)
 
 x = Symbol('x')
@@ -12,3 +12,10 @@ def test_CondSet():
     assert 3*pi not in sin_sols_principal
     assert 5 in ConditionSet(Lambda(x, x**2 > 4), S.Reals)
     assert 1 not in ConditionSet(Lambda(x, x**2 > 4), S.Reals)
+
+
+def test_CondSet_intersect():
+    input_conditionset = ConditionSet(Lambda(x, x**2 > 4), Interval(1, 4, False, False))
+    other_domain = Interval(0, 3, False, False)
+    output_conditionset = ConditionSet(Lambda(x, x**2 > 4), Interval(1, 3, False, False))
+    assert Intersection(input_conditionset, other_domain) == output_conditionset
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index afb659e205..004509eea5 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -14,7 +14,7 @@
 
 from sympy.polys.rootoftools import RootOf
 
-from sympy.sets import FiniteSet
+from sympy.sets import (FiniteSet, ConditionSet)
 
 from sympy.utilities.pytest import XFAIL, raises, skip
 from sympy.utilities.randtest import verify_numerically as tn
@@ -409,7 +409,8 @@ def test_solve_sqrt_3():
     eq = -sqrt((m - q)**2 + (-m/(2*q) + S(1)/2)**2) + sqrt((-m**2/2 - sqrt(
         4*m**4 - 4*m**2 + 8*m + 1)/4 - S(1)/4)**2 + (m**2/2 - m - sqrt(
             4*m**4 - 4*m**2 + 8*m + 1)/4 - S(1)/4)**2)
-    raises(NotImplementedError, lambda: solveset_real(eq, q))
+    unsolved_object = ConditionSet(Lambda(q, Eq((-2*sqrt(4*q**2*(m - q)**2 + (-m + q)**2) + sqrt((-2*m**2 - sqrt(4*m**4 - 4*m**2 + 8*m + 1) - 1)**2 + (2*m**2 - 4*m - sqrt(4*m**4 - 4*m**2 + 8*m + 1) - 1)**2)*Abs(q))/Abs(q), 0)), S.Reals)
+    assert solveset_real(eq, q) == unsolved_object
 
 
 def test_solve_polynomial_symbolic_param():
@@ -683,7 +684,9 @@ def test_solve_invalid_sol():
 
 
 def test_solve_complex_unsolvable():
-    raises(NotImplementedError, lambda: solveset_complex(cos(x) - S.Half, x))
+    unsolved_object = ConditionSet(Lambda(x, Eq(2*cos(x) - 1, 0)), S.Complexes)
+    solution = solveset_complex(cos(x) - S.Half, x)
+    assert solution == unsolved_object
 
 
 @XFAIL
@@ -808,6 +811,29 @@ def test_solveset():
                                                   S.Integers)
 
 
+def test_conditonset():
+    assert solveset(Eq(sin(x)**2 + cos(x)**2, 1), x, domain=S.Reals) == \
+        ConditionSet(Lambda(x, True), S.Reals)
+
+    assert solveset(Eq(x**2 + x*sin(x), 1), x, domain=S.Reals) == \
+        ConditionSet(Lambda(x, Eq(x*(x + sin(x)) - 1, 0)), S.Reals)
+
+    assert solveset(Eq(sin(Abs(x)), 1), x, domain=S.Reals) == \
+        ConditionSet(Lambda(x, Eq(sin(Abs(x)) - 1, 0)), S.Reals)
+
+    assert solveset(Eq(-I*(exp(I*x) - exp(-I*x))/2, 1), x) == \
+        imageset(Lambda(n, 2*n*pi + pi/2), S.Integers)
+
+    assert solveset(x + sin(x) > 1, x, domain=S.Reals) == \
+        ConditionSet(Lambda(x, x + sin(x) > 1), S.Reals)
+
+
+@XFAIL
+def test_conditionset_equality():
+    ''' Checking equality of different representations of ConditionSet'''
+    assert solveset(Eq(tan(x), y), x) == ConditionSet(Lambda(x, Eq(tan(x), y)), S.Complexes)
+
+
 def test_solveset_domain():
     x = Symbol('x')
 
@@ -820,7 +846,9 @@ def test_improve_coverage():
     from sympy.solvers.solveset import _has_rational_power
     x = Symbol('x')
     y = exp(x+1/x**2)
-    raises(NotImplementedError, lambda: solveset(y**2+y, x, S.Reals))
+    solution = solveset(y**2+y, x, S.Reals)
+    unsolved_object = ConditionSet(Lambda(x, Eq((exp((x**3 + 1)/x**2) + 1)*exp((x**3 + 1)/x**2), 0)), S.Reals)
+    assert solution == unsolved_object
 
     assert _has_rational_power(sin(x)*exp(x) + 1, x) == (False, S.One)
     assert _has_rational_power((sin(x)**2)*(exp(x) + 1)**3, x) == (False, S.One)