Repository: sympy/sympy
Pull request: Make EmptySet the instance rather than the class
<!-- 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. -->

Closes #16575
Fixes #16337

#### Brief description of what is fixed or changed

Makes EmptySet (as imported from sympy) be the singleton instance of the emptyset rather than the class. This brings EmptySet into line with other classes like Reals.

#### 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 -->
* sets
    * EmptySet (as imported from sympy) is now the singleton instance of the empty set rather than the class.
<!-- END RELEASE NOTES -->


Problem statement:
EmptySet and UniversalSet without S
I had thought we cleaned up all the S objects, but I guess not. 

Right now we have

```py
>>> from sympy import *
>>> EmptySet
<class 'sympy.sets.sets.EmptySet'>
>>> UniversalSet
UniversalSet()
```

There are two issues:

- `EmptySet` should be the instance, not the class.

- UniversalSet should not print with `()`.

Hints from issue discussion:
I am taking up the issue. Should I make a new instance that will be called from the class ``EmptySet`` ? or instead return ``EmptySet`` as the instance not the class? 
Specifically `from sympy import EmptySet` should import the instance, not the class. Look at how it works for things like `UniversalSet` in `sympy/sets/__init__.py`.
Ok,thank you
``EmptySet`` as well as ``UniversalSet`` has been registered on ``sympy register`` . But unlike ``UniversalSet``, it hasn't been installed.It can be confirmed from ``__getattr__`` module in ``sympy/core/singleton.py`` the code is

``class_to_install = self._classes_to_install[name]``
``value_to_install = class_to_install()``

by ``list(S._classes_to_install)`` we will get
``['BooleanTrue', 'BooleanFalse', 'EmptySet', 'EmptySequence']``
These above classes haven't been installed although registered in ``sympy register`` . 

Since ``EmptySet`` hasn't been installed,during mapping of instances in ``Singleton class`` as ``metaclass`` ,``__call__`` is invoked at first 

def __call__(self, *args, **kwargs):
        # Called when application code says SomeClass(), where SomeClass is a
        # class of which Singleton is the metaclas.
        # __call__ is invoked first, before __new__() and __init__().
        if self not in Singleton._instances:
            Singleton._instances[self] = \
                super(Singleton, self).__call__(*args, **kwargs)
                # Invokes the standard constructor of SomeClass.
        return Singleton._instances[self]

which returns the ``class`` of ``EmptySet`` in ``sympy/sets/sets.py ``.






Apparently I added the `type` exception to the `test_singleton.py` `test_names_in_namespace`. But the only places where this happens are `EmptySet` and `EmptySequence`. I think we should fix both of those to be the instances, and remove the `type` check from that test. 
This is not a problem of S objects.
``from sympy import *``
``print(S.EmptySet)``
gives output:
``EmptySet()``
``from sympy import *``
``print(EmptySet)``
gives output
``<class 'sympy.sets.sets.EmptySet'>``
May be this is the problem because it has not been declared separately as ``UniversalSet`` in the ``__init__ module ``.
Because EmptySet is in all_classes and 
``from sympy.core.singleton import Singleton``
``print(Singleton._instances)``
gives output 
``{<class 'sympy.core.numbers.Infinity'>: oo, <class 'sympy.core.numbers.NaN'>: nan, <class 'sympy.core.numbers.ComplexInfinity'>: zoo, <class 'sympy.core.numbers.Exp1'>: E, <class 'sympy.core.numbers.Pi'>: pi, <class 'sympy.core.numbers.ImaginaryUnit'>: I, <class 'sympy.core.numbers.Zero'>: 0, <class 'sympy.core.numbers.One'>: 1, <class 'sympy.core.numbers.NegativeOne'>: -1, <class 'sympy.logic.boolalg.BooleanTrue'>: True, <class 'sympy.logic.boolalg.BooleanFalse'>: False, <class 'sympy.core.numbers.Catalan'>: Catalan, <class 'sympy.core.numbers.EulerGamma'>: EulerGamma, <class 'sympy.core.numbers.GoldenRatio'>: GoldenRatio, <class 'sympy.core.numbers.TribonacciConstant'>: TribonacciConstant, <class 'sympy.sets.sets.UniversalSet'>: UniversalSet(), <class 'sympy.core.numbers.NegativeInfinity'>: -oo, <class 'sympy.sets.fancysets.Reals'>: Reals, <class 'sympy.sets.fancysets.Naturals'>: Naturals, <class 'sympy.sets.fancysets.Naturals0'>: Naturals0, <class 'sympy.sets.fancysets.Integers'>: Integers, <class 'sympy.functions.elementary.miscellaneous.IdentityFunction'>: Lambda(_x, _x), <class 'sympy.sets.sets.EmptySet'>: EmptySet(), <class 'sympy.core.numbers.Half'>: 1/2, <class 'sympy.sets.fancysets.Complexes'>: S.Complexes}
``
which shows EmptySet() as well as UniversalSet() are the instances

Yes, it needs to be added to the `__init__.py` file. `S` (`Singleton`) classes automatically have instances that are the same name as the class. However, the class name is not really needed, as it can always be accessed with `type(UniversalSet)` and so on.
We might even want to move the redefinition from `__init__py` to the files themselves. That way if someone does `from sympy.sets.sets import EmptySet` it will be the same as `from sympy import EmptySet`. And the same for ones in sympy/core. 
Ok sure . I am creating a pr for that

Should UniversalSet print in any special way or just as "plain text"?

Commit message: Use EmptySet not EmptySet.__class__()

Patch to review:
diff --git a/sympy/categories/baseclasses.py b/sympy/categories/baseclasses.py
index 3b80fae1ba9c..464b8168eaae 100644
--- a/sympy/categories/baseclasses.py
+++ b/sympy/categories/baseclasses.py
@@ -444,7 +444,7 @@ class Category(Basic):
     ========
     Diagram
     """
-    def __new__(cls, name, objects=EmptySet(), commutative_diagrams=EmptySet()):
+    def __new__(cls, name, objects=EmptySet, commutative_diagrams=EmptySet):
         if not name:
             raise ValueError("A Category cannot have an empty name.")
 
@@ -615,7 +615,7 @@ def _add_morphism_closure(morphisms, morphism, props, add_identities=True,
                 return
 
             if add_identities:
-                empty = EmptySet()
+                empty = EmptySet
 
                 id_dom = IdentityMorphism(morphism.domain)
                 id_cod = IdentityMorphism(morphism.codomain)
@@ -635,7 +635,7 @@ def _add_morphism_closure(morphisms, morphism, props, add_identities=True,
             if isinstance(morphism, CompositeMorphism) and recurse_composites:
                 # This is a composite morphism, add its components as
                 # well.
-                empty = EmptySet()
+                empty = EmptySet
                 for component in morphism.components:
                     Diagram._add_morphism_closure(morphisms, component, empty,
                                                   add_identities)
@@ -685,7 +685,7 @@ def __new__(cls, *args):
 
         # Here we will keep track of the objects which appear in the
         # premises.
-        objects = EmptySet()
+        objects = EmptySet
 
         if len(args) >= 1:
             # We've got some premises in the arguments.
@@ -694,7 +694,7 @@ def __new__(cls, *args):
             if isinstance(premises_arg, list):
                 # The user has supplied a list of morphisms, none of
                 # which have any attributes.
-                empty = EmptySet()
+                empty = EmptySet
 
                 for morphism in premises_arg:
                     objects |= FiniteSet(morphism.domain, morphism.codomain)
@@ -714,7 +714,7 @@ def __new__(cls, *args):
             if isinstance(conclusions_arg, list):
                 # The user has supplied a list of morphisms, none of
                 # which have any attributes.
-                empty = EmptySet()
+                empty = EmptySet
 
                 for morphism in conclusions_arg:
                     # Check that no new objects appear in conclusions.
@@ -838,8 +838,8 @@ def hom(self, A, B):
         ========
         Object, Morphism
         """
-        premises = EmptySet()
-        conclusions = EmptySet()
+        premises = EmptySet
+        conclusions = EmptySet
 
         for morphism in self.premises.keys():
             if (morphism.domain == A) and (morphism.codomain == B):
diff --git a/sympy/categories/tests/test_baseclasses.py b/sympy/categories/tests/test_baseclasses.py
index 334f95679c97..5b124757c8f4 100644
--- a/sympy/categories/tests/test_baseclasses.py
+++ b/sympy/categories/tests/test_baseclasses.py
@@ -92,7 +92,7 @@ def test_diagram():
     id_A = IdentityMorphism(A)
     id_B = IdentityMorphism(B)
 
-    empty = EmptySet()
+    empty = EmptySet
 
     # Test the addition of identities.
     d1 = Diagram([f])
diff --git a/sympy/core/tests/test_sympify.py b/sympy/core/tests/test_sympify.py
index 719d0db0a777..94219f90d1d5 100644
--- a/sympy/core/tests/test_sympify.py
+++ b/sympy/core/tests/test_sympify.py
@@ -573,7 +573,7 @@ def test_Range():
 def test_sympify_set():
     n = Symbol('n')
     assert sympify({n}) == FiniteSet(n)
-    assert sympify(set()) == EmptySet()
+    assert sympify(set()) == EmptySet
 
 
 def test_sympify_numpy():
diff --git a/sympy/geometry/tests/test_geometrysets.py b/sympy/geometry/tests/test_geometrysets.py
index 412a62ac7a9a..1a01a0c7c97d 100644
--- a/sympy/geometry/tests/test_geometrysets.py
+++ b/sympy/geometry/tests/test_geometrysets.py
@@ -20,7 +20,7 @@ def test_booleans():
     assert Intersection(l1, l2).equals(l1)
     assert Intersection(l1, l4) == FiniteSet(Point(1,1))
     assert Intersection(Union(l1, l4), l3) == FiniteSet(Point(Rational(-1, 3), Rational(-1, 3)), Point(5, 1))
-    assert Intersection(l1, FiniteSet(Point(7,-7))) == EmptySet()
+    assert Intersection(l1, FiniteSet(Point(7,-7))) == EmptySet
     assert Intersection(Circle(Point(0,0), 3), Line(p1,p2)) == FiniteSet(Point(-3,0), Point(3,0))
     assert Intersection(l1, FiniteSet(p1)) == FiniteSet(p1)
     assert Union(l1, FiniteSet(p1)) == l1
diff --git a/sympy/sets/__init__.py b/sympy/sets/__init__.py
index c3c400e9ae84..3cb1008020bc 100644
--- a/sympy/sets/__init__.py
+++ b/sympy/sets/__init__.py
@@ -10,6 +10,7 @@
 Naturals = S.Naturals
 Naturals0 = S.Naturals0
 UniversalSet = S.UniversalSet
+EmptySet = S.EmptySet
 Integers = S.Integers
 Rationals = S.Rationals
 del S
diff --git a/sympy/sets/handlers/functions.py b/sympy/sets/handlers/functions.py
index c4b6423c62cb..365b0060ca0b 100644
--- a/sympy/sets/handlers/functions.py
+++ b/sympy/sets/handlers/functions.py
@@ -132,7 +132,7 @@ def _set_function(f, x):
     else:
         return ImageSet(Lambda(_x, f(_x)), x)
 
-@dispatch(FunctionUnion, EmptySet)
+@dispatch(FunctionUnion, type(EmptySet))
 def _set_function(f, x):
     return x
 
diff --git a/sympy/sets/handlers/intersection.py b/sympy/sets/handlers/intersection.py
index aa192ab233bb..f9bb62da796c 100644
--- a/sympy/sets/handlers/intersection.py
+++ b/sympy/sets/handlers/intersection.py
@@ -238,10 +238,18 @@ def intersection_sets(self, other):
         if gm is not None:
             fn = self.lamda.expr
             n = self.lamda.variables[0]
-            solns = list(diophantine(fn - gm, syms=(n, m)))
-            if len(solns) == 0:
-                return EmptySet()
-            elif len(solns) != 1:
+            # Diophantine sorts the solutions according to the alphabetic
+            # order of the variable names, since the result should not depend
+            # on the variable name, they are replaced by the dummy variables
+            # below
+            a, b = Dummy('a'), Dummy('b')
+            f, g = fn.subs(n, a), gm.subs(m, b)
+            solns_set = diophantine(f - g)
+            if solns_set == set():
+                return EmptySet
+            solns = list(diophantine(f - g))
+
+            if len(solns) != 1:
                 return
             else:
                 soln, solm = solns[0]
@@ -395,7 +403,7 @@ def intersection_sets(a, b):
 
     return Interval(start, end, left_open, right_open)
 
-@dispatch(EmptySet, Set)
+@dispatch(type(EmptySet), Set)
 def intersection_sets(a, b):
     return S.EmptySet
 
diff --git a/sympy/sets/handlers/union.py b/sympy/sets/handlers/union.py
index e84befe0686f..1bcc8a9d5fa1 100644
--- a/sympy/sets/handlers/union.py
+++ b/sympy/sets/handlers/union.py
@@ -28,7 +28,7 @@ def union_sets(a, b):
             return ComplexRegion(Union(a.sets, b.sets), polar=True)
     return None
 
-@dispatch(EmptySet, Set)
+@dispatch(type(EmptySet), Set)
 def union_sets(a, b):
     return b
 
diff --git a/sympy/sets/sets.py b/sympy/sets/sets.py
index 0fdfce23413d..29087bffb28f 100644
--- a/sympy/sets/sets.py
+++ b/sympy/sets/sets.py
@@ -240,7 +240,7 @@ def symmetric_difference(self, other):
         Union(Interval.open(-oo, 1), Interval.open(10, oo))
 
         >>> from sympy import S, EmptySet
-        >>> S.Reals.symmetric_difference(EmptySet())
+        >>> S.Reals.symmetric_difference(EmptySet)
         Reals
 
         References
@@ -466,7 +466,8 @@ def powerset(self):
 
         A power set of an empty set:
 
-        >>> A = EmptySet()
+        >>> from sympy import FiniteSet, EmptySet
+        >>> A = EmptySet
         >>> A.powerset()
         {EmptySet()}
 
@@ -474,7 +475,7 @@ def powerset(self):
 
         >>> A = FiniteSet(1, 2)
         >>> a, b, c = FiniteSet(1), FiniteSet(2), FiniteSet(1, 2)
-        >>> A.powerset() == FiniteSet(a, b, c, EmptySet())
+        >>> A.powerset() == FiniteSet(a, b, c, EmptySet)
         True
 
         A power set of an interval:
diff --git a/sympy/sets/tests/test_conditionset.py b/sympy/sets/tests/test_conditionset.py
index 95983f2c24f9..7417bd1f8f83 100644
--- a/sympy/sets/tests/test_conditionset.py
+++ b/sympy/sets/tests/test_conditionset.py
@@ -69,8 +69,8 @@ def test_issue_9849():
 
 def test_simplified_FiniteSet_in_CondSet():
     assert ConditionSet(x, And(x < 1, x > -3), FiniteSet(0, 1, 2)) == FiniteSet(0)
-    assert ConditionSet(x, x < 0, FiniteSet(0, 1, 2)) == EmptySet()
-    assert ConditionSet(x, And(x < -3), EmptySet()) == EmptySet()
+    assert ConditionSet(x, x < 0, FiniteSet(0, 1, 2)) == EmptySet
+    assert ConditionSet(x, And(x < -3), EmptySet) == EmptySet
     y = Symbol('y')
     assert (ConditionSet(x, And(x > 0), FiniteSet(-1, 0, 1, y)) ==
         Union(FiniteSet(1), ConditionSet(x, And(x > 0), FiniteSet(y))))
diff --git a/sympy/sets/tests/test_sets.py b/sympy/sets/tests/test_sets.py
index ae3c1e588f68..755f6ef4be21 100644
--- a/sympy/sets/tests/test_sets.py
+++ b/sympy/sets/tests/test_sets.py
@@ -259,8 +259,8 @@ def test_Complement():
     assert -1 in Complement(S.Reals, S.Naturals, evaluate=False)
     assert not 1 in Complement(S.Reals, S.Naturals, evaluate=False)
 
-    assert Complement(S.Integers, S.UniversalSet) == EmptySet()
-    assert S.UniversalSet.complement(S.Integers) == EmptySet()
+    assert Complement(S.Integers, S.UniversalSet) == EmptySet
+    assert S.UniversalSet.complement(S.Integers) == EmptySet
 
     assert (not 0 in S.Reals.intersect(S.Integers - FiniteSet(0)))
 
@@ -439,7 +439,7 @@ def test_issue_9623():
     c = FiniteSet(n)
 
     assert Intersection(a, b, c) == Intersection(b, c)
-    assert Intersection(Interval(1, 2), Interval(3, 4), FiniteSet(n)) == EmptySet()
+    assert Intersection(Interval(1, 2), Interval(3, 4), FiniteSet(n)) == EmptySet
 
 
 def test_is_disjoint():
@@ -1262,7 +1262,7 @@ def test_issue_10337():
 
 def test_issue_10326():
     bad = [
-        EmptySet(),
+        EmptySet,
         FiniteSet(1),
         Interval(1, 2),
         S.ComplexInfinity,
@@ -1311,8 +1311,8 @@ def test_issue_8257():
 
 
 def test_issue_10931():
-    assert S.Integers - S.Integers == EmptySet()
-    assert S.Integers - S.Reals == EmptySet()
+    assert S.Integers - S.Integers == EmptySet
+    assert S.Integers - S.Reals == EmptySet
 
 
 def test_issue_11174():
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 6e040c75bef8..17edf11de820 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -843,7 +843,7 @@ def solve_decomposition(f, symbol, domain):
             elif isinstance(y_s, Union):
                 iter_iset = y_s.args
 
-            elif isinstance(y_s, EmptySet):
+            elif isinstance(y_s, type(EmptySet)):
                 # y_s is not in the range of g in g_s, so no solution exists
                 #in the given domain
                 return y_s
@@ -893,12 +893,12 @@ def _solveset(f, symbol, domain, _check=False):
     solver = lambda f, x, domain=domain: _solveset(f, x, domain)
     inverter = lambda f, rhs, symbol: _invert(f, rhs, symbol, domain)
 
-    result = EmptySet()
+    result = EmptySet
 
     if f.expand().is_zero:
         return domain
     elif not f.has(symbol):
-        return EmptySet()
+        return EmptySet
     elif f.is_Mul and all(_is_finite_with_finite_vars(m, domain)
             for m in f.args):
         # if f(x) and g(x) are both finite we can say that the solution of
@@ -915,7 +915,7 @@ def _solveset(f, symbol, domain, _check=False):
         a = f.args[0]
         result = solveset_real(a > 0, symbol)
     elif f.is_Piecewise:
-        result = EmptySet()
+        result = EmptySet
         expr_set_pairs = f.as_expr_set_pairs(domain)
         for (expr, in_set) in expr_set_pairs:
             if in_set.is_Relational:
@@ -1142,7 +1142,7 @@ def _invert_modular(modterm, rhs, n, symbol):
 
     if abs(rhs) >= abs(m):
         # if rhs has value greater than value of m.
-        return symbol, EmptySet()
+        return symbol, EmptySet
 
     if a is symbol:
         return symbol, ImageSet(Lambda(n, m*n + rhs), S.Integers)
@@ -1189,10 +1189,10 @@ def _invert_modular(modterm, rhs, n, symbol):
             try:
                 remainder_list = nthroot_mod(rhs, expo, m, all_roots=True)
                 if remainder_list is None:
-                    return symbol, EmptySet()
+                    return symbol, EmptySet
             except (ValueError, NotImplementedError):
                 return modterm, rhs
-            g_n = EmptySet()
+            g_n = EmptySet
             for rem in remainder_list:
                 g_n += ImageSet(Lambda(n, m*n + rem), S.Integers)
             return base, g_n
@@ -1281,7 +1281,7 @@ def _solve_modular(f, symbol, domain):
         base_sets = g_n.base_sets
         sol_set = _solveset(f_x - lamda_expr, symbol, S.Integers)
         if isinstance(sol_set, FiniteSet):
-            tmp_sol = EmptySet()
+            tmp_sol = EmptySet
             for sol in sol_set:
                 tmp_sol += ImageSet(Lambda(lamda_vars, sol), *base_sets)
             sol_set = tmp_sol
@@ -3395,7 +3395,7 @@ def nonlinsolve(system, *symbols):
 
         # positive dimensional system
         res = _handle_positive_dimensional(polys, symbols, denominators)
-        if isinstance(res, EmptySet) and any(not p.domain.is_Exact for p in polys):
+        if isinstance(res, type(EmptySet)) and any(not p.domain.is_Exact for p in polys):
             raise NotImplementedError("Equation not in exact domain. Try converting to rational")
         else:
             return res
diff --git a/sympy/utilities/tests/test_wester.py b/sympy/utilities/tests/test_wester.py
index 6529f24731f7..e819c4ab3d7f 100644
--- a/sympy/utilities/tests/test_wester.py
+++ b/sympy/utilities/tests/test_wester.py
@@ -966,7 +966,7 @@ def test_M19():
 
 
 def test_M20():
-    assert solveset(sqrt(x**2 + 1) - x + 2, x) == EmptySet()
+    assert solveset(sqrt(x**2 + 1) - x + 2, x) == EmptySet
 
 
 def test_M21():


Code review comments:
diff_hunk: @@ -466,15 +466,16 @@ def powerset(self):
 
         A power set of an empty set:
 
-        >>> A = EmptySet()
+        >>> from sympy import FiniteSet, EmptySet
+        >>> A = EmptySet
         >>> A.powerset()
         {EmptySet()}
path: sympy/sets/sets.py
text: @user1:
This printing is needs to be fixed, right?

@author:
That's fixed now. Some other doctests needed changing.