Repository: sympy/sympy

Issue:
Solve simplest algebraic equations with dummy parameter
Very strange behaviour of `solve` command in SymPy, when I try to solve quadratic equation with parameter `a` (which is dummy here, because solution doesn't depends on it):
```
from sympy import *
x, a = symbols("x a")
eq = Eq(0, (4 - 4*x + x**2)/(4*a**2))
print(solve(eq, x))
print(solve(simplify(eq), x))
```
Output:
```
[2 - sqrt(a**2 - 1)/a, 2 + sqrt(a**2 - 1)/a]
[2]
```
Just `solve` gives two (!) solutions, which depend on `a`. After symplifying it gives only solution `x=2`, which is correct. What happens? Command `solveset` works correctly, but I am interested in using `solve` command. My SymPy version is 1.8. 

Discussion:

Patch:
diff --git a/sympy/abc.py b/sympy/abc.py
index 177e8902e7..050f3da126 100644
--- a/sympy/abc.py
+++ b/sympy/abc.py
@@ -57,7 +57,6 @@
 
 from .core import Symbol, symbols
 from .core.alphabets import greeks
-from sympy.parsing.sympy_parser import null
 
 ##### Symbol definitions #####
 
@@ -99,13 +98,13 @@
 while ns:
     _k, _ = ns.popitem()
     if _k in _greek:
-        _clash2[_k] = null
+        _clash2[_k] = None
         _greek.remove(_k)
     elif _k in _latin:
-        _clash1[_k] = null
+        _clash1[_k] = None
         _latin.remove(_k)
 _clash = {}
 _clash.update(_clash1)
 _clash.update(_clash2)
 
-del _latin, _greek, Symbol, _k, null
+del _latin, _greek, Symbol, _k
diff --git a/sympy/core/add.py b/sympy/core/add.py
index 890d313692..41b5728fb8 100644
--- a/sympy/core/add.py
+++ b/sympy/core/add.py
@@ -613,6 +613,8 @@ def as_numer_denom(self):
         """
         # clear rational denominator
         content, expr = self.primitive()
+        if not isinstance(expr, Add):
+            return Mul(content, expr, evaluate=False).as_numer_denom()
         ncon, dcon = content.as_numer_denom()
 
         # collect numerators and denominators of the terms
diff --git a/sympy/core/relational.py b/sympy/core/relational.py
index 7a4be766fd..3f551dda33 100644
--- a/sympy/core/relational.py
+++ b/sympy/core/relational.py
@@ -578,7 +578,8 @@ def _eval_rewrite_as_Add(self, *args, **kwargs):
         the result set pass `evaluate=True` to give L - R;
         if `evaluate=None` then terms in L and R will not cancel
         but they will be listed in canonical order; otherwise
-        non-canonical args will be returned.
+        non-canonical args will be returned. If one side is 0, the
+        non-zero side will be returned.
 
         Examples
         ========
@@ -595,6 +596,10 @@ def _eval_rewrite_as_Add(self, *args, **kwargs):
         """
         from .add import _unevaluated_Add, Add
         L, R = args
+        if L == 0:
+            return R
+        if R == 0:
+            return L
         evaluate = kwargs.get('evaluate', True)
         if evaluate:
             # allow cancellation of args
diff --git a/sympy/parsing/sympy_parser.py b/sympy/parsing/sympy_parser.py
index ae79514670..e403654cb4 100644
--- a/sympy/parsing/sympy_parser.py
+++ b/sympy/parsing/sympy_parser.py
@@ -20,8 +20,6 @@
 from sympy.functions.elementary.miscellaneous import Max, Min
 
 
-null = ''
-
 def _token_splittable(token):
     """
     Predicate for whether a token name can be split into multiple tokens.
@@ -561,11 +559,11 @@ def auto_symbol(tokens, local_dict, global_dict):
                     or (prevTok[0] == OP and prevTok[1] in ('(', ',')
                         and nextTokNum == OP and nextTokVal == '=')
                     # the name has already been defined
-                    or name in local_dict and local_dict[name] is not null):
+                    or name in local_dict and local_dict[name] is not None):
                 result.append((NAME, name))
                 continue
             elif name in local_dict:
-                local_dict.setdefault(null, set()).add(name)
+                local_dict.setdefault(None, set()).add(name)
                 if nextTokVal == '(':
                     local_dict[name] = Function(name)
                 else:
@@ -1048,8 +1046,6 @@ def parse_expr(s, local_dict=None, transformations=standard_transformations,
         local_dict = {}
     elif not isinstance(local_dict, dict):
         raise TypeError('expecting local_dict to be a dict')
-    elif null in local_dict:
-        raise ValueError('cannot use "" in local_dict')
 
     if global_dict is None:
         global_dict = {}
@@ -1094,13 +1090,13 @@ def parse_expr(s, local_dict=None, transformations=standard_transformations,
     try:
         rv = eval_expr(code, local_dict, global_dict)
         # restore neutral definitions for names
-        for i in local_dict.pop(null, ()):
-            local_dict[i] = null
+        for i in local_dict.pop(None, ()):
+            local_dict[i] = None
         return rv
     except Exception as e:
         # restore neutral definitions for names
-        for i in local_dict.pop(null, ()):
-            local_dict[i] = null
+        for i in local_dict.pop(None, ()):
+            local_dict[i] = None
         raise e from ValueError(f"Error from parse_expr with transformed code: {code!r}")
 
 


Test patch:
diff --git a/.github/workflows/runtests.yml b/.github/workflows/runtests.yml
index eed7f8f15d..b9b0cd2102 100644
--- a/.github/workflows/runtests.yml
+++ b/.github/workflows/runtests.yml
@@ -272,7 +272,7 @@ jobs:
         with:
           python-version: '3.9'
       - run: release/aptinstall.sh
-      - run: pip install -r doc/requirements.txt
+      - run: pip install -r release/requirements.txt
       - run: bin/test_sphinx.sh
 
 
diff --git a/sympy/core/tests/test_expr.py b/sympy/core/tests/test_expr.py
index b332f89d33..eccb45ad90 100644
--- a/sympy/core/tests/test_expr.py
+++ b/sympy/core/tests/test_expr.py
@@ -827,6 +827,10 @@ def test_as_numer_denom():
     assert ((A*B*C)**-1).as_numer_denom() == ((A*B*C)**-1, 1)
     assert ((A*B*C)**-1/x).as_numer_denom() == ((A*B*C)**-1, x)
 
+    # the following morphs from Add to Mul during processing
+    assert Add(0, (x + y)/z/-2, evaluate=False).as_numer_denom(
+        ) == (-x - y, 2*z)
+
 
 def test_trunc():
     import math
diff --git a/sympy/core/tests/test_relational.py b/sympy/core/tests/test_relational.py
index 21fcaf9711..57d5d65d2d 100644
--- a/sympy/core/tests/test_relational.py
+++ b/sympy/core/tests/test_relational.py
@@ -1034,6 +1034,9 @@ def test_Equality_rewrite_as_Add():
     assert eq.rewrite(Add) == 2*x
     assert eq.rewrite(Add, evaluate=None).args == (x, x, y, -y)
     assert eq.rewrite(Add, evaluate=False).args == (x, y, x, -y)
+    for e in (True, False, None):
+        assert Eq(x, 0, evaluate=e).rewrite(Add) == x
+        assert Eq(0, x, evaluate=e).rewrite(Add) == x
 
 
 def test_issue_15847():
diff --git a/sympy/core/tests/test_sympify.py b/sympy/core/tests/test_sympify.py
index 4e736bc341..3e4e9e0189 100644
--- a/sympy/core/tests/test_sympify.py
+++ b/sympy/core/tests/test_sympify.py
@@ -12,7 +12,6 @@
 from sympy.functions.elementary.trigonometric import (cos, sin)
 from sympy.logic.boolalg import (false, Or, true, Xor)
 from sympy.matrices.dense import Matrix
-from sympy.parsing.sympy_parser import null
 from sympy.polys.polytools import Poly
 from sympy.printing.repr import srepr
 from sympy.sets.fancysets import Range
@@ -541,7 +540,7 @@ def test_issue_6046():
     assert len(S('pi + x', locals=_clash2).free_symbols) == 2
     # but not both
     raises(TypeError, lambda: S('pi + pi(x)', locals=_clash2))
-    assert all(set(i.values()) == {null} for i in (
+    assert all(set(i.values()) == {None} for i in (
         _clash, _clash1, _clash2))
 
 
diff --git a/sympy/parsing/tests/test_sympy_parser.py b/sympy/parsing/tests/test_sympy_parser.py
index a03dd73952..2e326c576d 100644
--- a/sympy/parsing/tests/test_sympy_parser.py
+++ b/sympy/parsing/tests/test_sympy_parser.py
@@ -330,9 +330,3 @@ def test_builtins():
     for built_in_func_call, sympy_func_call in cases:
         assert parse_expr(built_in_func_call) == parse_expr(sympy_func_call)
     assert str(parse_expr('pow(38, -1, 97)')) == '23'
-
-
-def test_issue_22822():
-    raises(ValueError, lambda: parse_expr('x', {'': 1}))
-    data = {'some_parameter': None}
-    assert parse_expr('some_parameter is None', data) is True