Repository: sympy/sympy
Pull request: _unevaluated_Mul watches for nc args
<!-- 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://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->
fix #27091

#### Brief description of what is fixed or changed


#### Other comments

The function can now be used without reservation by `separatevars` when there are noncommutative objects; the `if` was guarding against using the older form of the routine which failed for nc elements.

#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers. Formerly, `log(-x)` incorrectly gave `-log(x)`.

* physics.units
  * Corrected a semantical error in the conversion between volt and statvolt which
    reported the volt as being larger than the statvolt.

or if no release note(s) should be included use:

NO ENTRY

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 -->


Problem statement:
_unevaluated_Mul re-orders non-commutative factors
```python
from sympy import S
from sympy.core.mul import _unevaluated_Mul as f
from sympy.abc import x
A, B = symbols("A B", commutative=False)
>>> f(x, A, B, S(2), A).args
(2, x, A, A, B)  # should be (2, x, A, B, A)
```

Hints from issue discussion:

Commit message: use _unevaluated_Mul with as_independent

Patch to review:
diff --git a/sympy/core/expr.py b/sympy/core/expr.py
index 7c4015e0f0e5..97112c6390eb 100644
--- a/sympy/core/expr.py
+++ b/sympy/core/expr.py
@@ -1889,9 +1889,7 @@ def has(e):
                     depend.extend(nc[i:])
                     break
                 indep.append(n)
-            return Mul(*indep), (
-                Mul(*depend, evaluate=False) if nc else
-                _unevaluated_Mul(*depend))
+            return _unevaluated_Mul(*indep), _unevaluated_Mul(*depend)
 
     def as_real_imag(self, deep=True, **hints):
         """Performs complex expansion on 'self' and returns a tuple
diff --git a/sympy/core/mul.py b/sympy/core/mul.py
index 2881402950a6..1e5197e04af5 100644
--- a/sympy/core/mul.py
+++ b/sympy/core/mul.py
@@ -15,9 +15,9 @@
 from .parameters import global_parameters
 from .kind import KindDispatcher
 from .traversal import bottom_up
-
 from sympy.utilities.iterables import sift
 
+
 # internal marker to indicate:
 #   "there are still non-commutative objects -- don't forget to process them"
 class NC_Marker:
@@ -65,27 +65,25 @@ def _unevaluated_Mul(*args):
     False
 
     """
-    args = list(args)
-    newargs = []
+    cargs = []
     ncargs = []
+    args = list(args)
     co = S.One
-    while args:
-        a = args.pop()
+    for a in args:
         if a.is_Mul:
-            c, nc = a.args_cnc()
-            args.extend(c)
-            if nc:
-                ncargs.append(Mul._from_args(nc))
+            a_c, a_nc = a.args_cnc()
+            args.extend(a_c)  # grow args
+            ncargs.extend(a_nc)
         elif a.is_Number:
             co *= a
+        elif a.is_commutative:
+            cargs.append(a)
         else:
-            newargs.append(a)
-    _mulsort(newargs)
+            ncargs.append(a)
+    _mulsort(cargs)
     if co is not S.One:
-        newargs.insert(0, co)
-    if ncargs:
-        newargs.append(Mul._from_args(ncargs))
-    return Mul._from_args(newargs)
+        cargs.insert(0, co)
+    return Mul._from_args(cargs+ncargs)
 
 
 class Mul(Expr, AssocOp):
diff --git a/sympy/core/tests/test_expr.py b/sympy/core/tests/test_expr.py
index a37c7818228a..233e00a557e5 100644
--- a/sympy/core/tests/test_expr.py
+++ b/sympy/core/tests/test_expr.py
@@ -7,7 +7,7 @@
     UnevaluatedExpr)
 from sympy.core.function import (Function, expand, WildFunction,
     AppliedUndef, Derivative, diff, Subs)
-from sympy.core.mul import Mul
+from sympy.core.mul import Mul, _unevaluated_Mul
 from sympy.core.numbers import (NumberSymbol, E, zoo, oo, Float, I,
     Rational, nan, Integer, Number, pi, _illegal)
 from sympy.core.power import Pow
@@ -2291,3 +2291,9 @@ def test_format():
 
 def test_issue_24045():
     assert powsimp(exp(a)/((c*a - c*b)*(Float(1.0)*c*a - Float(1.0)*c*b)))  # doesn't raise
+
+
+def test__unevaluated_Mul():
+    A, B = symbols('A B', commutative=False)
+    assert _unevaluated_Mul(x, A, B, S(2), A).args == (2, x, A, B, A)
+    assert _unevaluated_Mul(-x*A*B, S(2), A).args == (-2, x, A, B, A)
diff --git a/sympy/polys/partfrac.py b/sympy/polys/partfrac.py
index a04e5de9b78a..dedc1bf0fba4 100644
--- a/sympy/polys/partfrac.py
+++ b/sympy/polys/partfrac.py
@@ -18,7 +18,7 @@ def apart(f, x=None, full=False, **options):
 
     Given a rational function ``f``, computes the partial fraction
     decomposition of ``f``. Two algorithms are available: One is based on the
-    undertermined coefficients method, the other is Bronstein's full partial
+    undetermined coefficients method, the other is Bronstein's full partial
     fraction decomposition algorithm.
 
     The undetermined coefficients method (selected by ``full=False``) uses


Code review comments:
diff_hunk: @@ -1889,9 +1889,7 @@ def has(e):
                     depend.extend(nc[i:])
                     break
                 indep.append(n)
-            return Mul(*indep), (
-                Mul(*depend, evaluate=False) if nc else
-                _unevaluated_Mul(*depend))
+            return _unevaluated_Mul(*indep), _unevaluated_Mul(*depend)
path: sympy/core/expr.py
text: @author:
```suggestion
            return Mul(*indep), _unevaluated_Mul(*depend)
```