1570 lines
50 KiB
Python
1570 lines
50 KiB
Python
"""Tools for manipulating of large commutative expressions. """
|
|
|
|
from .add import Add
|
|
from .mul import Mul, _keep_coeff
|
|
from .power import Pow
|
|
from .basic import Basic
|
|
from .expr import Expr
|
|
from .function import expand_power_exp
|
|
from .sympify import sympify
|
|
from .numbers import Rational, Integer, Number, I, equal_valued
|
|
from .singleton import S
|
|
from .sorting import default_sort_key, ordered
|
|
from .symbol import Dummy
|
|
from .traversal import preorder_traversal
|
|
from .coreerrors import NonCommutativeExpression
|
|
from .containers import Tuple, Dict
|
|
from sympy.external.gmpy import SYMPY_INTS
|
|
from sympy.utilities.iterables import (common_prefix, common_suffix,
|
|
variations, iterable, is_sequence)
|
|
|
|
from collections import defaultdict
|
|
from typing import Tuple as tTuple
|
|
|
|
|
|
_eps = Dummy(positive=True)
|
|
|
|
|
|
def _isnumber(i):
|
|
return isinstance(i, (SYMPY_INTS, float)) or i.is_Number
|
|
|
|
|
|
def _monotonic_sign(self):
|
|
"""Return the value closest to 0 that ``self`` may have if all symbols
|
|
are signed and the result is uniformly the same sign for all values of symbols.
|
|
If a symbol is only signed but not known to be an
|
|
integer or the result is 0 then a symbol representative of the sign of self
|
|
will be returned. Otherwise, None is returned if a) the sign could be positive
|
|
or negative or b) self is not in one of the following forms:
|
|
|
|
- L(x, y, ...) + A: a function linear in all symbols x, y, ... with an
|
|
additive constant; if A is zero then the function can be a monomial whose
|
|
sign is monotonic over the range of the variables, e.g. (x + 1)**3 if x is
|
|
nonnegative.
|
|
- A/L(x, y, ...) + B: the inverse of a function linear in all symbols x, y, ...
|
|
that does not have a sign change from positive to negative for any set
|
|
of values for the variables.
|
|
- M(x, y, ...) + A: a monomial M whose factors are all signed and a constant, A.
|
|
- A/M(x, y, ...) + B: the inverse of a monomial and constants A and B.
|
|
- P(x): a univariate polynomial
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import _monotonic_sign as F
|
|
>>> from sympy import Dummy
|
|
>>> nn = Dummy(integer=True, nonnegative=True)
|
|
>>> p = Dummy(integer=True, positive=True)
|
|
>>> p2 = Dummy(integer=True, positive=True)
|
|
>>> F(nn + 1)
|
|
1
|
|
>>> F(p - 1)
|
|
_nneg
|
|
>>> F(nn*p + 1)
|
|
1
|
|
>>> F(p2*p + 1)
|
|
2
|
|
>>> F(nn - 1) # could be negative, zero or positive
|
|
"""
|
|
if not self.is_extended_real:
|
|
return
|
|
|
|
if (-self).is_Symbol:
|
|
rv = _monotonic_sign(-self)
|
|
return rv if rv is None else -rv
|
|
|
|
if not self.is_Add and self.as_numer_denom()[1].is_number:
|
|
s = self
|
|
if s.is_prime:
|
|
if s.is_odd:
|
|
return Integer(3)
|
|
else:
|
|
return Integer(2)
|
|
elif s.is_composite:
|
|
if s.is_odd:
|
|
return Integer(9)
|
|
else:
|
|
return Integer(4)
|
|
elif s.is_positive:
|
|
if s.is_even:
|
|
if s.is_prime is False:
|
|
return Integer(4)
|
|
else:
|
|
return Integer(2)
|
|
elif s.is_integer:
|
|
return S.One
|
|
else:
|
|
return _eps
|
|
elif s.is_extended_negative:
|
|
if s.is_even:
|
|
return Integer(-2)
|
|
elif s.is_integer:
|
|
return S.NegativeOne
|
|
else:
|
|
return -_eps
|
|
if s.is_zero or s.is_extended_nonpositive or s.is_extended_nonnegative:
|
|
return S.Zero
|
|
return None
|
|
|
|
# univariate polynomial
|
|
free = self.free_symbols
|
|
if len(free) == 1:
|
|
if self.is_polynomial():
|
|
from sympy.polys.polytools import real_roots
|
|
from sympy.polys.polyroots import roots
|
|
from sympy.polys.polyerrors import PolynomialError
|
|
x = free.pop()
|
|
x0 = _monotonic_sign(x)
|
|
if x0 in (_eps, -_eps):
|
|
x0 = S.Zero
|
|
if x0 is not None:
|
|
d = self.diff(x)
|
|
if d.is_number:
|
|
currentroots = []
|
|
else:
|
|
try:
|
|
currentroots = real_roots(d)
|
|
except (PolynomialError, NotImplementedError):
|
|
currentroots = [r for r in roots(d, x) if r.is_extended_real]
|
|
y = self.subs(x, x0)
|
|
if x.is_nonnegative and all(
|
|
(r - x0).is_nonpositive for r in currentroots):
|
|
if y.is_nonnegative and d.is_positive:
|
|
if y:
|
|
return y if y.is_positive else Dummy('pos', positive=True)
|
|
else:
|
|
return Dummy('nneg', nonnegative=True)
|
|
if y.is_nonpositive and d.is_negative:
|
|
if y:
|
|
return y if y.is_negative else Dummy('neg', negative=True)
|
|
else:
|
|
return Dummy('npos', nonpositive=True)
|
|
elif x.is_nonpositive and all(
|
|
(r - x0).is_nonnegative for r in currentroots):
|
|
if y.is_nonnegative and d.is_negative:
|
|
if y:
|
|
return Dummy('pos', positive=True)
|
|
else:
|
|
return Dummy('nneg', nonnegative=True)
|
|
if y.is_nonpositive and d.is_positive:
|
|
if y:
|
|
return Dummy('neg', negative=True)
|
|
else:
|
|
return Dummy('npos', nonpositive=True)
|
|
else:
|
|
n, d = self.as_numer_denom()
|
|
den = None
|
|
if n.is_number:
|
|
den = _monotonic_sign(d)
|
|
elif not d.is_number:
|
|
if _monotonic_sign(n) is not None:
|
|
den = _monotonic_sign(d)
|
|
if den is not None and (den.is_positive or den.is_negative):
|
|
v = n*den
|
|
if v.is_positive:
|
|
return Dummy('pos', positive=True)
|
|
elif v.is_nonnegative:
|
|
return Dummy('nneg', nonnegative=True)
|
|
elif v.is_negative:
|
|
return Dummy('neg', negative=True)
|
|
elif v.is_nonpositive:
|
|
return Dummy('npos', nonpositive=True)
|
|
return None
|
|
|
|
# multivariate
|
|
c, a = self.as_coeff_Add()
|
|
v = None
|
|
if not a.is_polynomial():
|
|
# F/A or A/F where A is a number and F is a signed, rational monomial
|
|
n, d = a.as_numer_denom()
|
|
if not (n.is_number or d.is_number):
|
|
return
|
|
if (
|
|
a.is_Mul or a.is_Pow) and \
|
|
a.is_rational and \
|
|
all(p.exp.is_Integer for p in a.atoms(Pow) if p.is_Pow) and \
|
|
(a.is_positive or a.is_negative):
|
|
v = S.One
|
|
for ai in Mul.make_args(a):
|
|
if ai.is_number:
|
|
v *= ai
|
|
continue
|
|
reps = {}
|
|
for x in ai.free_symbols:
|
|
reps[x] = _monotonic_sign(x)
|
|
if reps[x] is None:
|
|
return
|
|
v *= ai.subs(reps)
|
|
elif c:
|
|
# signed linear expression
|
|
if not any(p for p in a.atoms(Pow) if not p.is_number) and (a.is_nonpositive or a.is_nonnegative):
|
|
free = list(a.free_symbols)
|
|
p = {}
|
|
for i in free:
|
|
v = _monotonic_sign(i)
|
|
if v is None:
|
|
return
|
|
p[i] = v or (_eps if i.is_nonnegative else -_eps)
|
|
v = a.xreplace(p)
|
|
if v is not None:
|
|
rv = v + c
|
|
if v.is_nonnegative and rv.is_positive:
|
|
return rv.subs(_eps, 0)
|
|
if v.is_nonpositive and rv.is_negative:
|
|
return rv.subs(_eps, 0)
|
|
|
|
|
|
def decompose_power(expr: Expr) -> tTuple[Expr, int]:
|
|
"""
|
|
Decompose power into symbolic base and integer exponent.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import decompose_power
|
|
>>> from sympy.abc import x, y
|
|
>>> from sympy import exp
|
|
|
|
>>> decompose_power(x)
|
|
(x, 1)
|
|
>>> decompose_power(x**2)
|
|
(x, 2)
|
|
>>> decompose_power(exp(2*y/3))
|
|
(exp(y/3), 2)
|
|
|
|
"""
|
|
base, exp = expr.as_base_exp()
|
|
|
|
if exp.is_Number:
|
|
if exp.is_Rational:
|
|
if not exp.is_Integer:
|
|
base = Pow(base, Rational(1, exp.q)) # type: ignore
|
|
e = exp.p # type: ignore
|
|
else:
|
|
base, e = expr, 1
|
|
else:
|
|
exp, tail = exp.as_coeff_Mul(rational=True)
|
|
|
|
if exp is S.NegativeOne:
|
|
base, e = Pow(base, tail), -1
|
|
elif exp is not S.One:
|
|
# todo: after dropping python 3.7 support, use overload and Literal
|
|
# in as_coeff_Mul to make exp Rational, and remove these 2 ignores
|
|
tail = _keep_coeff(Rational(1, exp.q), tail) # type: ignore
|
|
base, e = Pow(base, tail), exp.p # type: ignore
|
|
else:
|
|
base, e = expr, 1
|
|
|
|
return base, e
|
|
|
|
|
|
def decompose_power_rat(expr: Expr) -> tTuple[Expr, Rational]:
|
|
"""
|
|
Decompose power into symbolic base and rational exponent;
|
|
if the exponent is not a Rational, then separate only the
|
|
integer coefficient.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import decompose_power_rat
|
|
>>> from sympy.abc import x
|
|
>>> from sympy import sqrt, exp
|
|
|
|
>>> decompose_power_rat(sqrt(x))
|
|
(x, 1/2)
|
|
>>> decompose_power_rat(exp(-3*x/2))
|
|
(exp(x/2), -3)
|
|
|
|
"""
|
|
_ = base, exp = expr.as_base_exp()
|
|
return _ if exp.is_Rational else decompose_power(expr)
|
|
|
|
|
|
class Factors:
|
|
"""Efficient representation of ``f_1*f_2*...*f_n``."""
|
|
|
|
__slots__ = ('factors', 'gens')
|
|
|
|
def __init__(self, factors=None): # Factors
|
|
"""Initialize Factors from dict or expr.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x
|
|
>>> from sympy import I
|
|
>>> e = 2*x**3
|
|
>>> Factors(e)
|
|
Factors({2: 1, x: 3})
|
|
>>> Factors(e.as_powers_dict())
|
|
Factors({2: 1, x: 3})
|
|
>>> f = _
|
|
>>> f.factors # underlying dictionary
|
|
{2: 1, x: 3}
|
|
>>> f.gens # base of each factor
|
|
frozenset({2, x})
|
|
>>> Factors(0)
|
|
Factors({0: 1})
|
|
>>> Factors(I)
|
|
Factors({I: 1})
|
|
|
|
Notes
|
|
=====
|
|
|
|
Although a dictionary can be passed, only minimal checking is
|
|
performed: powers of -1 and I are made canonical.
|
|
|
|
"""
|
|
if isinstance(factors, (SYMPY_INTS, float)):
|
|
factors = S(factors)
|
|
if isinstance(factors, Factors):
|
|
factors = factors.factors.copy()
|
|
elif factors in (None, S.One):
|
|
factors = {}
|
|
elif factors is S.Zero or factors == 0:
|
|
factors = {S.Zero: S.One}
|
|
elif isinstance(factors, Number):
|
|
n = factors
|
|
factors = {}
|
|
if n < 0:
|
|
factors[S.NegativeOne] = S.One
|
|
n = -n
|
|
if n is not S.One:
|
|
if n.is_Float or n.is_Integer or n is S.Infinity:
|
|
factors[n] = S.One
|
|
elif n.is_Rational:
|
|
# since we're processing Numbers, the denominator is
|
|
# stored with a negative exponent; all other factors
|
|
# are left .
|
|
if n.p != 1:
|
|
factors[Integer(n.p)] = S.One
|
|
factors[Integer(n.q)] = S.NegativeOne
|
|
else:
|
|
raise ValueError('Expected Float|Rational|Integer, not %s' % n)
|
|
elif isinstance(factors, Basic) and not factors.args:
|
|
factors = {factors: S.One}
|
|
elif isinstance(factors, Expr):
|
|
c, nc = factors.args_cnc()
|
|
i = c.count(I)
|
|
for _ in range(i):
|
|
c.remove(I)
|
|
factors = dict(Mul._from_args(c).as_powers_dict())
|
|
# Handle all rational Coefficients
|
|
for f in list(factors.keys()):
|
|
if isinstance(f, Rational) and not isinstance(f, Integer):
|
|
p, q = Integer(f.p), Integer(f.q)
|
|
factors[p] = (factors[p] if p in factors else S.Zero) + factors[f]
|
|
factors[q] = (factors[q] if q in factors else S.Zero) - factors[f]
|
|
factors.pop(f)
|
|
if i:
|
|
factors[I] = factors.get(I, S.Zero) + i
|
|
if nc:
|
|
factors[Mul(*nc, evaluate=False)] = S.One
|
|
else:
|
|
factors = factors.copy() # /!\ should be dict-like
|
|
|
|
# tidy up -/+1 and I exponents if Rational
|
|
|
|
handle = [k for k in factors if k is I or k in (-1, 1)]
|
|
if handle:
|
|
i1 = S.One
|
|
for k in handle:
|
|
if not _isnumber(factors[k]):
|
|
continue
|
|
i1 *= k**factors.pop(k)
|
|
if i1 is not S.One:
|
|
for a in i1.args if i1.is_Mul else [i1]: # at worst, -1.0*I*(-1)**e
|
|
if a is S.NegativeOne:
|
|
factors[a] = S.One
|
|
elif a is I:
|
|
factors[I] = S.One
|
|
elif a.is_Pow:
|
|
factors[a.base] = factors.get(a.base, S.Zero) + a.exp
|
|
elif equal_valued(a, 1):
|
|
factors[a] = S.One
|
|
elif equal_valued(a, -1):
|
|
factors[-a] = S.One
|
|
factors[S.NegativeOne] = S.One
|
|
else:
|
|
raise ValueError('unexpected factor in i1: %s' % a)
|
|
|
|
self.factors = factors
|
|
keys = getattr(factors, 'keys', None)
|
|
if keys is None:
|
|
raise TypeError('expecting Expr or dictionary')
|
|
self.gens = frozenset(keys())
|
|
|
|
def __hash__(self): # Factors
|
|
keys = tuple(ordered(self.factors.keys()))
|
|
values = [self.factors[k] for k in keys]
|
|
return hash((keys, values))
|
|
|
|
def __repr__(self): # Factors
|
|
return "Factors({%s})" % ', '.join(
|
|
['%s: %s' % (k, v) for k, v in ordered(self.factors.items())])
|
|
|
|
@property
|
|
def is_zero(self): # Factors
|
|
"""
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> Factors(0).is_zero
|
|
True
|
|
"""
|
|
f = self.factors
|
|
return len(f) == 1 and S.Zero in f
|
|
|
|
@property
|
|
def is_one(self): # Factors
|
|
"""
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> Factors(1).is_one
|
|
True
|
|
"""
|
|
return not self.factors
|
|
|
|
def as_expr(self): # Factors
|
|
"""Return the underlying expression.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y
|
|
>>> Factors((x*y**2).as_powers_dict()).as_expr()
|
|
x*y**2
|
|
|
|
"""
|
|
|
|
args = []
|
|
for factor, exp in self.factors.items():
|
|
if exp != 1:
|
|
if isinstance(exp, Integer):
|
|
b, e = factor.as_base_exp()
|
|
e = _keep_coeff(exp, e)
|
|
args.append(b**e)
|
|
else:
|
|
args.append(factor**exp)
|
|
else:
|
|
args.append(factor)
|
|
return Mul(*args)
|
|
|
|
def mul(self, other): # Factors
|
|
"""Return Factors of ``self * other``.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y, z
|
|
>>> a = Factors((x*y**2).as_powers_dict())
|
|
>>> b = Factors((x*y/z).as_powers_dict())
|
|
>>> a.mul(b)
|
|
Factors({x: 2, y: 3, z: -1})
|
|
>>> a*b
|
|
Factors({x: 2, y: 3, z: -1})
|
|
"""
|
|
if not isinstance(other, Factors):
|
|
other = Factors(other)
|
|
if any(f.is_zero for f in (self, other)):
|
|
return Factors(S.Zero)
|
|
factors = dict(self.factors)
|
|
|
|
for factor, exp in other.factors.items():
|
|
if factor in factors:
|
|
exp = factors[factor] + exp
|
|
|
|
if not exp:
|
|
del factors[factor]
|
|
continue
|
|
|
|
factors[factor] = exp
|
|
|
|
return Factors(factors)
|
|
|
|
def normal(self, other):
|
|
"""Return ``self`` and ``other`` with ``gcd`` removed from each.
|
|
The only differences between this and method ``div`` is that this
|
|
is 1) optimized for the case when there are few factors in common and
|
|
2) this does not raise an error if ``other`` is zero.
|
|
|
|
See Also
|
|
========
|
|
div
|
|
|
|
"""
|
|
if not isinstance(other, Factors):
|
|
other = Factors(other)
|
|
if other.is_zero:
|
|
return (Factors(), Factors(S.Zero))
|
|
if self.is_zero:
|
|
return (Factors(S.Zero), Factors())
|
|
|
|
self_factors = dict(self.factors)
|
|
other_factors = dict(other.factors)
|
|
|
|
for factor, self_exp in self.factors.items():
|
|
try:
|
|
other_exp = other.factors[factor]
|
|
except KeyError:
|
|
continue
|
|
|
|
exp = self_exp - other_exp
|
|
|
|
if not exp:
|
|
del self_factors[factor]
|
|
del other_factors[factor]
|
|
elif _isnumber(exp):
|
|
if exp > 0:
|
|
self_factors[factor] = exp
|
|
del other_factors[factor]
|
|
else:
|
|
del self_factors[factor]
|
|
other_factors[factor] = -exp
|
|
else:
|
|
r = self_exp.extract_additively(other_exp)
|
|
if r is not None:
|
|
if r:
|
|
self_factors[factor] = r
|
|
del other_factors[factor]
|
|
else: # should be handled already
|
|
del self_factors[factor]
|
|
del other_factors[factor]
|
|
else:
|
|
sc, sa = self_exp.as_coeff_Add()
|
|
if sc:
|
|
oc, oa = other_exp.as_coeff_Add()
|
|
diff = sc - oc
|
|
if diff > 0:
|
|
self_factors[factor] -= oc
|
|
other_exp = oa
|
|
elif diff < 0:
|
|
self_factors[factor] -= sc
|
|
other_factors[factor] -= sc
|
|
other_exp = oa - diff
|
|
else:
|
|
self_factors[factor] = sa
|
|
other_exp = oa
|
|
if other_exp:
|
|
other_factors[factor] = other_exp
|
|
else:
|
|
del other_factors[factor]
|
|
|
|
return Factors(self_factors), Factors(other_factors)
|
|
|
|
def div(self, other): # Factors
|
|
"""Return ``self`` and ``other`` with ``gcd`` removed from each.
|
|
This is optimized for the case when there are many factors in common.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y, z
|
|
>>> from sympy import S
|
|
|
|
>>> a = Factors((x*y**2).as_powers_dict())
|
|
>>> a.div(a)
|
|
(Factors({}), Factors({}))
|
|
>>> a.div(x*z)
|
|
(Factors({y: 2}), Factors({z: 1}))
|
|
|
|
The ``/`` operator only gives ``quo``:
|
|
|
|
>>> a/x
|
|
Factors({y: 2})
|
|
|
|
Factors treats its factors as though they are all in the numerator, so
|
|
if you violate this assumption the results will be correct but will
|
|
not strictly correspond to the numerator and denominator of the ratio:
|
|
|
|
>>> a.div(x/z)
|
|
(Factors({y: 2}), Factors({z: -1}))
|
|
|
|
Factors is also naive about bases: it does not attempt any denesting
|
|
of Rational-base terms, for example the following does not become
|
|
2**(2*x)/2.
|
|
|
|
>>> Factors(2**(2*x + 2)).div(S(8))
|
|
(Factors({2: 2*x + 2}), Factors({8: 1}))
|
|
|
|
factor_terms can clean up such Rational-bases powers:
|
|
|
|
>>> from sympy import factor_terms
|
|
>>> n, d = Factors(2**(2*x + 2)).div(S(8))
|
|
>>> n.as_expr()/d.as_expr()
|
|
2**(2*x + 2)/8
|
|
>>> factor_terms(_)
|
|
2**(2*x)/2
|
|
|
|
"""
|
|
quo, rem = dict(self.factors), {}
|
|
|
|
if not isinstance(other, Factors):
|
|
other = Factors(other)
|
|
if other.is_zero:
|
|
raise ZeroDivisionError
|
|
if self.is_zero:
|
|
return (Factors(S.Zero), Factors())
|
|
|
|
for factor, exp in other.factors.items():
|
|
if factor in quo:
|
|
d = quo[factor] - exp
|
|
if _isnumber(d):
|
|
if d <= 0:
|
|
del quo[factor]
|
|
|
|
if d >= 0:
|
|
if d:
|
|
quo[factor] = d
|
|
|
|
continue
|
|
|
|
exp = -d
|
|
|
|
else:
|
|
r = quo[factor].extract_additively(exp)
|
|
if r is not None:
|
|
if r:
|
|
quo[factor] = r
|
|
else: # should be handled already
|
|
del quo[factor]
|
|
else:
|
|
other_exp = exp
|
|
sc, sa = quo[factor].as_coeff_Add()
|
|
if sc:
|
|
oc, oa = other_exp.as_coeff_Add()
|
|
diff = sc - oc
|
|
if diff > 0:
|
|
quo[factor] -= oc
|
|
other_exp = oa
|
|
elif diff < 0:
|
|
quo[factor] -= sc
|
|
other_exp = oa - diff
|
|
else:
|
|
quo[factor] = sa
|
|
other_exp = oa
|
|
if other_exp:
|
|
rem[factor] = other_exp
|
|
else:
|
|
assert factor not in rem
|
|
continue
|
|
|
|
rem[factor] = exp
|
|
|
|
return Factors(quo), Factors(rem)
|
|
|
|
def quo(self, other): # Factors
|
|
"""Return numerator Factor of ``self / other``.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y, z
|
|
>>> a = Factors((x*y**2).as_powers_dict())
|
|
>>> b = Factors((x*y/z).as_powers_dict())
|
|
>>> a.quo(b) # same as a/b
|
|
Factors({y: 1})
|
|
"""
|
|
return self.div(other)[0]
|
|
|
|
def rem(self, other): # Factors
|
|
"""Return denominator Factors of ``self / other``.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y, z
|
|
>>> a = Factors((x*y**2).as_powers_dict())
|
|
>>> b = Factors((x*y/z).as_powers_dict())
|
|
>>> a.rem(b)
|
|
Factors({z: -1})
|
|
>>> a.rem(a)
|
|
Factors({})
|
|
"""
|
|
return self.div(other)[1]
|
|
|
|
def pow(self, other): # Factors
|
|
"""Return self raised to a non-negative integer power.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y
|
|
>>> a = Factors((x*y**2).as_powers_dict())
|
|
>>> a**2
|
|
Factors({x: 2, y: 4})
|
|
|
|
"""
|
|
if isinstance(other, Factors):
|
|
other = other.as_expr()
|
|
if other.is_Integer:
|
|
other = int(other)
|
|
if isinstance(other, SYMPY_INTS) and other >= 0:
|
|
factors = {}
|
|
|
|
if other:
|
|
for factor, exp in self.factors.items():
|
|
factors[factor] = exp*other
|
|
|
|
return Factors(factors)
|
|
else:
|
|
raise ValueError("expected non-negative integer, got %s" % other)
|
|
|
|
def gcd(self, other): # Factors
|
|
"""Return Factors of ``gcd(self, other)``. The keys are
|
|
the intersection of factors with the minimum exponent for
|
|
each factor.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y, z
|
|
>>> a = Factors((x*y**2).as_powers_dict())
|
|
>>> b = Factors((x*y/z).as_powers_dict())
|
|
>>> a.gcd(b)
|
|
Factors({x: 1, y: 1})
|
|
"""
|
|
if not isinstance(other, Factors):
|
|
other = Factors(other)
|
|
if other.is_zero:
|
|
return Factors(self.factors)
|
|
|
|
factors = {}
|
|
|
|
for factor, exp in self.factors.items():
|
|
factor, exp = sympify(factor), sympify(exp)
|
|
if factor in other.factors:
|
|
lt = (exp - other.factors[factor]).is_negative
|
|
if lt == True:
|
|
factors[factor] = exp
|
|
elif lt == False:
|
|
factors[factor] = other.factors[factor]
|
|
|
|
return Factors(factors)
|
|
|
|
def lcm(self, other): # Factors
|
|
"""Return Factors of ``lcm(self, other)`` which are
|
|
the union of factors with the maximum exponent for
|
|
each factor.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.core.exprtools import Factors
|
|
>>> from sympy.abc import x, y, z
|
|
>>> a = Factors((x*y**2).as_powers_dict())
|
|
>>> b = Factors((x*y/z).as_powers_dict())
|
|
>>> a.lcm(b)
|
|
Factors({x: 1, y: 2, z: -1})
|
|
"""
|
|
if not isinstance(other, Factors):
|
|
other = Factors(other)
|
|
if any(f.is_zero for f in (self, other)):
|
|
return Factors(S.Zero)
|
|
|
|
factors = dict(self.factors)
|
|
|
|
for factor, exp in other.factors.items():
|
|
if factor in factors:
|
|
exp = max(exp, factors[factor])
|
|
|
|
factors[factor] = exp
|
|
|
|
return Factors(factors)
|
|
|
|
def __mul__(self, other): # Factors
|
|
return self.mul(other)
|
|
|
|
def __divmod__(self, other): # Factors
|
|
return self.div(other)
|
|
|
|
def __truediv__(self, other): # Factors
|
|
return self.quo(other)
|
|
|
|
def __mod__(self, other): # Factors
|
|
return self.rem(other)
|
|
|
|
def __pow__(self, other): # Factors
|
|
return self.pow(other)
|
|
|
|
def __eq__(self, other): # Factors
|
|
if not isinstance(other, Factors):
|
|
other = Factors(other)
|
|
return self.factors == other.factors
|
|
|
|
def __ne__(self, other): # Factors
|
|
return not self == other
|
|
|
|
|
|
class Term:
|
|
"""Efficient representation of ``coeff*(numer/denom)``. """
|
|
|
|
__slots__ = ('coeff', 'numer', 'denom')
|
|
|
|
def __init__(self, term, numer=None, denom=None): # Term
|
|
if numer is None and denom is None:
|
|
if not term.is_commutative:
|
|
raise NonCommutativeExpression(
|
|
'commutative expression expected')
|
|
|
|
coeff, factors = term.as_coeff_mul()
|
|
numer, denom = defaultdict(int), defaultdict(int)
|
|
|
|
for factor in factors:
|
|
base, exp = decompose_power(factor)
|
|
|
|
if base.is_Add:
|
|
cont, base = base.primitive()
|
|
coeff *= cont**exp
|
|
|
|
if exp > 0:
|
|
numer[base] += exp
|
|
else:
|
|
denom[base] += -exp
|
|
|
|
numer = Factors(numer)
|
|
denom = Factors(denom)
|
|
else:
|
|
coeff = term
|
|
|
|
if numer is None:
|
|
numer = Factors()
|
|
|
|
if denom is None:
|
|
denom = Factors()
|
|
|
|
self.coeff = coeff
|
|
self.numer = numer
|
|
self.denom = denom
|
|
|
|
def __hash__(self): # Term
|
|
return hash((self.coeff, self.numer, self.denom))
|
|
|
|
def __repr__(self): # Term
|
|
return "Term(%s, %s, %s)" % (self.coeff, self.numer, self.denom)
|
|
|
|
def as_expr(self): # Term
|
|
return self.coeff*(self.numer.as_expr()/self.denom.as_expr())
|
|
|
|
def mul(self, other): # Term
|
|
coeff = self.coeff*other.coeff
|
|
numer = self.numer.mul(other.numer)
|
|
denom = self.denom.mul(other.denom)
|
|
|
|
numer, denom = numer.normal(denom)
|
|
|
|
return Term(coeff, numer, denom)
|
|
|
|
def inv(self): # Term
|
|
return Term(1/self.coeff, self.denom, self.numer)
|
|
|
|
def quo(self, other): # Term
|
|
return self.mul(other.inv())
|
|
|
|
def pow(self, other): # Term
|
|
if other < 0:
|
|
return self.inv().pow(-other)
|
|
else:
|
|
return Term(self.coeff ** other,
|
|
self.numer.pow(other),
|
|
self.denom.pow(other))
|
|
|
|
def gcd(self, other): # Term
|
|
return Term(self.coeff.gcd(other.coeff),
|
|
self.numer.gcd(other.numer),
|
|
self.denom.gcd(other.denom))
|
|
|
|
def lcm(self, other): # Term
|
|
return Term(self.coeff.lcm(other.coeff),
|
|
self.numer.lcm(other.numer),
|
|
self.denom.lcm(other.denom))
|
|
|
|
def __mul__(self, other): # Term
|
|
if isinstance(other, Term):
|
|
return self.mul(other)
|
|
else:
|
|
return NotImplemented
|
|
|
|
def __truediv__(self, other): # Term
|
|
if isinstance(other, Term):
|
|
return self.quo(other)
|
|
else:
|
|
return NotImplemented
|
|
|
|
def __pow__(self, other): # Term
|
|
if isinstance(other, SYMPY_INTS):
|
|
return self.pow(other)
|
|
else:
|
|
return NotImplemented
|
|
|
|
def __eq__(self, other): # Term
|
|
return (self.coeff == other.coeff and
|
|
self.numer == other.numer and
|
|
self.denom == other.denom)
|
|
|
|
def __ne__(self, other): # Term
|
|
return not self == other
|
|
|
|
|
|
def _gcd_terms(terms, isprimitive=False, fraction=True):
|
|
"""Helper function for :func:`gcd_terms`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
isprimitive : boolean, optional
|
|
If ``isprimitive`` is True then the call to primitive
|
|
for an Add will be skipped. This is useful when the
|
|
content has already been extracted.
|
|
|
|
fraction : boolean, optional
|
|
If ``fraction`` is True then the expression will appear over a common
|
|
denominator, the lcm of all term denominators.
|
|
"""
|
|
|
|
if isinstance(terms, Basic) and not isinstance(terms, Tuple):
|
|
terms = Add.make_args(terms)
|
|
|
|
terms = list(map(Term, [t for t in terms if t]))
|
|
|
|
# there is some simplification that may happen if we leave this
|
|
# here rather than duplicate it before the mapping of Term onto
|
|
# the terms
|
|
if len(terms) == 0:
|
|
return S.Zero, S.Zero, S.One
|
|
|
|
if len(terms) == 1:
|
|
cont = terms[0].coeff
|
|
numer = terms[0].numer.as_expr()
|
|
denom = terms[0].denom.as_expr()
|
|
|
|
else:
|
|
cont = terms[0]
|
|
for term in terms[1:]:
|
|
cont = cont.gcd(term)
|
|
|
|
for i, term in enumerate(terms):
|
|
terms[i] = term.quo(cont)
|
|
|
|
if fraction:
|
|
denom = terms[0].denom
|
|
|
|
for term in terms[1:]:
|
|
denom = denom.lcm(term.denom)
|
|
|
|
numers = []
|
|
for term in terms:
|
|
numer = term.numer.mul(denom.quo(term.denom))
|
|
numers.append(term.coeff*numer.as_expr())
|
|
else:
|
|
numers = [t.as_expr() for t in terms]
|
|
denom = Term(S.One).numer
|
|
|
|
cont = cont.as_expr()
|
|
numer = Add(*numers)
|
|
denom = denom.as_expr()
|
|
|
|
if not isprimitive and numer.is_Add:
|
|
_cont, numer = numer.primitive()
|
|
cont *= _cont
|
|
|
|
return cont, numer, denom
|
|
|
|
|
|
def gcd_terms(terms, isprimitive=False, clear=True, fraction=True):
|
|
"""Compute the GCD of ``terms`` and put them together.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
terms : Expr
|
|
Can be an expression or a non-Basic sequence of expressions
|
|
which will be handled as though they are terms from a sum.
|
|
|
|
isprimitive : bool, optional
|
|
If ``isprimitive`` is True the _gcd_terms will not run the primitive
|
|
method on the terms.
|
|
|
|
clear : bool, optional
|
|
It controls the removal of integers from the denominator of an Add
|
|
expression. When True (default), all numerical denominator will be cleared;
|
|
when False the denominators will be cleared only if all terms had numerical
|
|
denominators other than 1.
|
|
|
|
fraction : bool, optional
|
|
When True (default), will put the expression over a common
|
|
denominator.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import gcd_terms
|
|
>>> from sympy.abc import x, y
|
|
|
|
>>> gcd_terms((x + 1)**2*y + (x + 1)*y**2)
|
|
y*(x + 1)*(x + y + 1)
|
|
>>> gcd_terms(x/2 + 1)
|
|
(x + 2)/2
|
|
>>> gcd_terms(x/2 + 1, clear=False)
|
|
x/2 + 1
|
|
>>> gcd_terms(x/2 + y/2, clear=False)
|
|
(x + y)/2
|
|
>>> gcd_terms(x/2 + 1/x)
|
|
(x**2 + 2)/(2*x)
|
|
>>> gcd_terms(x/2 + 1/x, fraction=False)
|
|
(x + 2/x)/2
|
|
>>> gcd_terms(x/2 + 1/x, fraction=False, clear=False)
|
|
x/2 + 1/x
|
|
|
|
>>> gcd_terms(x/2/y + 1/x/y)
|
|
(x**2 + 2)/(2*x*y)
|
|
>>> gcd_terms(x/2/y + 1/x/y, clear=False)
|
|
(x**2/2 + 1)/(x*y)
|
|
>>> gcd_terms(x/2/y + 1/x/y, clear=False, fraction=False)
|
|
(x/2 + 1/x)/y
|
|
|
|
The ``clear`` flag was ignored in this case because the returned
|
|
expression was a rational expression, not a simple sum.
|
|
|
|
See Also
|
|
========
|
|
|
|
factor_terms, sympy.polys.polytools.terms_gcd
|
|
|
|
"""
|
|
def mask(terms):
|
|
"""replace nc portions of each term with a unique Dummy symbols
|
|
and return the replacements to restore them"""
|
|
args = [(a, []) if a.is_commutative else a.args_cnc() for a in terms]
|
|
reps = []
|
|
for i, (c, nc) in enumerate(args):
|
|
if nc:
|
|
nc = Mul(*nc)
|
|
d = Dummy()
|
|
reps.append((d, nc))
|
|
c.append(d)
|
|
args[i] = Mul(*c)
|
|
else:
|
|
args[i] = c
|
|
return args, dict(reps)
|
|
|
|
isadd = isinstance(terms, Add)
|
|
addlike = isadd or not isinstance(terms, Basic) and \
|
|
is_sequence(terms, include=set) and \
|
|
not isinstance(terms, Dict)
|
|
|
|
if addlike:
|
|
if isadd: # i.e. an Add
|
|
terms = list(terms.args)
|
|
else:
|
|
terms = sympify(terms)
|
|
terms, reps = mask(terms)
|
|
cont, numer, denom = _gcd_terms(terms, isprimitive, fraction)
|
|
numer = numer.xreplace(reps)
|
|
coeff, factors = cont.as_coeff_Mul()
|
|
if not clear:
|
|
c, _coeff = coeff.as_coeff_Mul()
|
|
if not c.is_Integer and not clear and numer.is_Add:
|
|
n, d = c.as_numer_denom()
|
|
_numer = numer/d
|
|
if any(a.as_coeff_Mul()[0].is_Integer
|
|
for a in _numer.args):
|
|
numer = _numer
|
|
coeff = n*_coeff
|
|
return _keep_coeff(coeff, factors*numer/denom, clear=clear)
|
|
|
|
if not isinstance(terms, Basic):
|
|
return terms
|
|
|
|
if terms.is_Atom:
|
|
return terms
|
|
|
|
if terms.is_Mul:
|
|
c, args = terms.as_coeff_mul()
|
|
return _keep_coeff(c, Mul(*[gcd_terms(i, isprimitive, clear, fraction)
|
|
for i in args]), clear=clear)
|
|
|
|
def handle(a):
|
|
# don't treat internal args like terms of an Add
|
|
if not isinstance(a, Expr):
|
|
if isinstance(a, Basic):
|
|
if not a.args:
|
|
return a
|
|
return a.func(*[handle(i) for i in a.args])
|
|
return type(a)([handle(i) for i in a])
|
|
return gcd_terms(a, isprimitive, clear, fraction)
|
|
|
|
if isinstance(terms, Dict):
|
|
return Dict(*[(k, handle(v)) for k, v in terms.args])
|
|
return terms.func(*[handle(i) for i in terms.args])
|
|
|
|
|
|
def _factor_sum_int(expr, **kwargs):
|
|
"""Return Sum or Integral object with factors that are not
|
|
in the wrt variables removed. In cases where there are additive
|
|
terms in the function of the object that are independent, the
|
|
object will be separated into two objects.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import Sum, factor_terms
|
|
>>> from sympy.abc import x, y
|
|
>>> factor_terms(Sum(x + y, (x, 1, 3)))
|
|
y*Sum(1, (x, 1, 3)) + Sum(x, (x, 1, 3))
|
|
>>> factor_terms(Sum(x*y, (x, 1, 3)))
|
|
y*Sum(x, (x, 1, 3))
|
|
|
|
Notes
|
|
=====
|
|
|
|
If a function in the summand or integrand is replaced
|
|
with a symbol, then this simplification should not be
|
|
done or else an incorrect result will be obtained when
|
|
the symbol is replaced with an expression that depends
|
|
on the variables of summation/integration:
|
|
|
|
>>> eq = Sum(y, (x, 1, 3))
|
|
>>> factor_terms(eq).subs(y, x).doit()
|
|
3*x
|
|
>>> eq.subs(y, x).doit()
|
|
6
|
|
"""
|
|
result = expr.function
|
|
if result == 0:
|
|
return S.Zero
|
|
limits = expr.limits
|
|
|
|
# get the wrt variables
|
|
wrt = {i.args[0] for i in limits}
|
|
|
|
# factor out any common terms that are independent of wrt
|
|
f = factor_terms(result, **kwargs)
|
|
i, d = f.as_independent(*wrt)
|
|
if isinstance(f, Add):
|
|
return i * expr.func(1, *limits) + expr.func(d, *limits)
|
|
else:
|
|
return i * expr.func(d, *limits)
|
|
|
|
|
|
def factor_terms(expr, radical=False, clear=False, fraction=False, sign=True):
|
|
"""Remove common factors from terms in all arguments without
|
|
changing the underlying structure of the expr. No expansion or
|
|
simplification (and no processing of non-commutatives) is performed.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
radical: bool, optional
|
|
If radical=True then a radical common to all terms will be factored
|
|
out of any Add sub-expressions of the expr.
|
|
|
|
clear : bool, optional
|
|
If clear=False (default) then coefficients will not be separated
|
|
from a single Add if they can be distributed to leave one or more
|
|
terms with integer coefficients.
|
|
|
|
fraction : bool, optional
|
|
If fraction=True (default is False) then a common denominator will be
|
|
constructed for the expression.
|
|
|
|
sign : bool, optional
|
|
If sign=True (default) then even if the only factor in common is a -1,
|
|
it will be factored out of the expression.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import factor_terms, Symbol
|
|
>>> from sympy.abc import x, y
|
|
>>> factor_terms(x + x*(2 + 4*y)**3)
|
|
x*(8*(2*y + 1)**3 + 1)
|
|
>>> A = Symbol('A', commutative=False)
|
|
>>> factor_terms(x*A + x*A + x*y*A)
|
|
x*(y*A + 2*A)
|
|
|
|
When ``clear`` is False, a rational will only be factored out of an
|
|
Add expression if all terms of the Add have coefficients that are
|
|
fractions:
|
|
|
|
>>> factor_terms(x/2 + 1, clear=False)
|
|
x/2 + 1
|
|
>>> factor_terms(x/2 + 1, clear=True)
|
|
(x + 2)/2
|
|
|
|
If a -1 is all that can be factored out, to *not* factor it out, the
|
|
flag ``sign`` must be False:
|
|
|
|
>>> factor_terms(-x - y)
|
|
-(x + y)
|
|
>>> factor_terms(-x - y, sign=False)
|
|
-x - y
|
|
>>> factor_terms(-2*x - 2*y, sign=False)
|
|
-2*(x + y)
|
|
|
|
See Also
|
|
========
|
|
|
|
gcd_terms, sympy.polys.polytools.terms_gcd
|
|
|
|
"""
|
|
def do(expr):
|
|
from sympy.concrete.summations import Sum
|
|
from sympy.integrals.integrals import Integral
|
|
is_iterable = iterable(expr)
|
|
|
|
if not isinstance(expr, Basic) or expr.is_Atom:
|
|
if is_iterable:
|
|
return type(expr)([do(i) for i in expr])
|
|
return expr
|
|
|
|
if expr.is_Pow or expr.is_Function or \
|
|
is_iterable or not hasattr(expr, 'args_cnc'):
|
|
args = expr.args
|
|
newargs = tuple([do(i) for i in args])
|
|
if newargs == args:
|
|
return expr
|
|
return expr.func(*newargs)
|
|
|
|
if isinstance(expr, (Sum, Integral)):
|
|
return _factor_sum_int(expr,
|
|
radical=radical, clear=clear,
|
|
fraction=fraction, sign=sign)
|
|
|
|
cont, p = expr.as_content_primitive(radical=radical, clear=clear)
|
|
if p.is_Add:
|
|
list_args = [do(a) for a in Add.make_args(p)]
|
|
# get a common negative (if there) which gcd_terms does not remove
|
|
if not any(a.as_coeff_Mul()[0].extract_multiplicatively(-1) is None
|
|
for a in list_args):
|
|
cont = -cont
|
|
list_args = [-a for a in list_args]
|
|
# watch out for exp(-(x+2)) which gcd_terms will change to exp(-x-2)
|
|
special = {}
|
|
for i, a in enumerate(list_args):
|
|
b, e = a.as_base_exp()
|
|
if e.is_Mul and e != Mul(*e.args):
|
|
list_args[i] = Dummy()
|
|
special[list_args[i]] = a
|
|
# rebuild p not worrying about the order which gcd_terms will fix
|
|
p = Add._from_args(list_args)
|
|
p = gcd_terms(p,
|
|
isprimitive=True,
|
|
clear=clear,
|
|
fraction=fraction).xreplace(special)
|
|
elif p.args:
|
|
p = p.func(
|
|
*[do(a) for a in p.args])
|
|
rv = _keep_coeff(cont, p, clear=clear, sign=sign)
|
|
return rv
|
|
expr = sympify(expr)
|
|
return do(expr)
|
|
|
|
|
|
def _mask_nc(eq, name=None):
|
|
"""
|
|
Return ``eq`` with non-commutative objects replaced with Dummy
|
|
symbols. A dictionary that can be used to restore the original
|
|
values is returned: if it is None, the expression is noncommutative
|
|
and cannot be made commutative. The third value returned is a list
|
|
of any non-commutative symbols that appear in the returned equation.
|
|
|
|
Explanation
|
|
===========
|
|
|
|
All non-commutative objects other than Symbols are replaced with
|
|
a non-commutative Symbol. Identical objects will be identified
|
|
by identical symbols.
|
|
|
|
If there is only 1 non-commutative object in an expression it will
|
|
be replaced with a commutative symbol. Otherwise, the non-commutative
|
|
entities are retained and the calling routine should handle
|
|
replacements in this case since some care must be taken to keep
|
|
track of the ordering of symbols when they occur within Muls.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
name : str
|
|
``name``, if given, is the name that will be used with numbered Dummy
|
|
variables that will replace the non-commutative objects and is mainly
|
|
used for doctesting purposes.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.secondquant import Commutator, NO, F, Fd
|
|
>>> from sympy import symbols
|
|
>>> from sympy.core.exprtools import _mask_nc
|
|
>>> from sympy.abc import x, y
|
|
>>> A, B, C = symbols('A,B,C', commutative=False)
|
|
|
|
One nc-symbol:
|
|
|
|
>>> _mask_nc(A**2 - x**2, 'd')
|
|
(_d0**2 - x**2, {_d0: A}, [])
|
|
|
|
Multiple nc-symbols:
|
|
|
|
>>> _mask_nc(A**2 - B**2, 'd')
|
|
(A**2 - B**2, {}, [A, B])
|
|
|
|
An nc-object with nc-symbols but no others outside of it:
|
|
|
|
>>> _mask_nc(1 + x*Commutator(A, B), 'd')
|
|
(_d0*x + 1, {_d0: Commutator(A, B)}, [])
|
|
>>> _mask_nc(NO(Fd(x)*F(y)), 'd')
|
|
(_d0, {_d0: NO(CreateFermion(x)*AnnihilateFermion(y))}, [])
|
|
|
|
Multiple nc-objects:
|
|
|
|
>>> eq = x*Commutator(A, B) + x*Commutator(A, C)*Commutator(A, B)
|
|
>>> _mask_nc(eq, 'd')
|
|
(x*_d0 + x*_d1*_d0, {_d0: Commutator(A, B), _d1: Commutator(A, C)}, [_d0, _d1])
|
|
|
|
Multiple nc-objects and nc-symbols:
|
|
|
|
>>> eq = A*Commutator(A, B) + B*Commutator(A, C)
|
|
>>> _mask_nc(eq, 'd')
|
|
(A*_d0 + B*_d1, {_d0: Commutator(A, B), _d1: Commutator(A, C)}, [_d0, _d1, A, B])
|
|
|
|
"""
|
|
name = name or 'mask'
|
|
# Make Dummy() append sequential numbers to the name
|
|
|
|
def numbered_names():
|
|
i = 0
|
|
while True:
|
|
yield name + str(i)
|
|
i += 1
|
|
|
|
names = numbered_names()
|
|
|
|
def Dummy(*args, **kwargs):
|
|
from .symbol import Dummy
|
|
return Dummy(next(names), *args, **kwargs)
|
|
|
|
expr = eq
|
|
if expr.is_commutative:
|
|
return eq, {}, []
|
|
|
|
# identify nc-objects; symbols and other
|
|
rep = []
|
|
nc_obj = set()
|
|
nc_syms = set()
|
|
pot = preorder_traversal(expr, keys=default_sort_key)
|
|
for i, a in enumerate(pot):
|
|
if any(a == r[0] for r in rep):
|
|
pot.skip()
|
|
elif not a.is_commutative:
|
|
if a.is_symbol:
|
|
nc_syms.add(a)
|
|
pot.skip()
|
|
elif not (a.is_Add or a.is_Mul or a.is_Pow):
|
|
nc_obj.add(a)
|
|
pot.skip()
|
|
|
|
# If there is only one nc symbol or object, it can be factored regularly
|
|
# but polys is going to complain, so replace it with a Dummy.
|
|
if len(nc_obj) == 1 and not nc_syms:
|
|
rep.append((nc_obj.pop(), Dummy()))
|
|
elif len(nc_syms) == 1 and not nc_obj:
|
|
rep.append((nc_syms.pop(), Dummy()))
|
|
|
|
# Any remaining nc-objects will be replaced with an nc-Dummy and
|
|
# identified as an nc-Symbol to watch out for
|
|
nc_obj = sorted(nc_obj, key=default_sort_key)
|
|
for n in nc_obj:
|
|
nc = Dummy(commutative=False)
|
|
rep.append((n, nc))
|
|
nc_syms.add(nc)
|
|
expr = expr.subs(rep)
|
|
|
|
nc_syms = list(nc_syms)
|
|
nc_syms.sort(key=default_sort_key)
|
|
return expr, {v: k for k, v in rep}, nc_syms
|
|
|
|
|
|
def factor_nc(expr):
|
|
"""Return the factored form of ``expr`` while handling non-commutative
|
|
expressions.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import factor_nc, Symbol
|
|
>>> from sympy.abc import x
|
|
>>> A = Symbol('A', commutative=False)
|
|
>>> B = Symbol('B', commutative=False)
|
|
>>> factor_nc((x**2 + 2*A*x + A**2).expand())
|
|
(x + A)**2
|
|
>>> factor_nc(((x + A)*(x + B)).expand())
|
|
(x + A)*(x + B)
|
|
"""
|
|
expr = sympify(expr)
|
|
if not isinstance(expr, Expr) or not expr.args:
|
|
return expr
|
|
if not expr.is_Add:
|
|
return expr.func(*[factor_nc(a) for a in expr.args])
|
|
expr = expr.func(*[expand_power_exp(i) for i in expr.args])
|
|
|
|
from sympy.polys.polytools import gcd, factor
|
|
expr, rep, nc_symbols = _mask_nc(expr)
|
|
|
|
if rep:
|
|
return factor(expr).subs(rep)
|
|
else:
|
|
args = [a.args_cnc() for a in Add.make_args(expr)]
|
|
c = g = l = r = S.One
|
|
hit = False
|
|
# find any commutative gcd term
|
|
for i, a in enumerate(args):
|
|
if i == 0:
|
|
c = Mul._from_args(a[0])
|
|
elif a[0]:
|
|
c = gcd(c, Mul._from_args(a[0]))
|
|
else:
|
|
c = S.One
|
|
if c is not S.One:
|
|
hit = True
|
|
c, g = c.as_coeff_Mul()
|
|
if g is not S.One:
|
|
for i, (cc, _) in enumerate(args):
|
|
cc = list(Mul.make_args(Mul._from_args(list(cc))/g))
|
|
args[i][0] = cc
|
|
for i, (cc, _) in enumerate(args):
|
|
if cc:
|
|
cc[0] = cc[0]/c
|
|
else:
|
|
cc = [1/c]
|
|
args[i][0] = cc
|
|
# find any noncommutative common prefix
|
|
for i, a in enumerate(args):
|
|
if i == 0:
|
|
n = a[1][:]
|
|
else:
|
|
n = common_prefix(n, a[1])
|
|
if not n:
|
|
# is there a power that can be extracted?
|
|
if not args[0][1]:
|
|
break
|
|
b, e = args[0][1][0].as_base_exp()
|
|
ok = False
|
|
if e.is_Integer:
|
|
for t in args:
|
|
if not t[1]:
|
|
break
|
|
bt, et = t[1][0].as_base_exp()
|
|
if et.is_Integer and bt == b:
|
|
e = min(e, et)
|
|
else:
|
|
break
|
|
else:
|
|
ok = hit = True
|
|
l = b**e
|
|
il = b**-e
|
|
for _ in args:
|
|
_[1][0] = il*_[1][0]
|
|
break
|
|
if not ok:
|
|
break
|
|
else:
|
|
hit = True
|
|
lenn = len(n)
|
|
l = Mul(*n)
|
|
for _ in args:
|
|
_[1] = _[1][lenn:]
|
|
# find any noncommutative common suffix
|
|
for i, a in enumerate(args):
|
|
if i == 0:
|
|
n = a[1][:]
|
|
else:
|
|
n = common_suffix(n, a[1])
|
|
if not n:
|
|
# is there a power that can be extracted?
|
|
if not args[0][1]:
|
|
break
|
|
b, e = args[0][1][-1].as_base_exp()
|
|
ok = False
|
|
if e.is_Integer:
|
|
for t in args:
|
|
if not t[1]:
|
|
break
|
|
bt, et = t[1][-1].as_base_exp()
|
|
if et.is_Integer and bt == b:
|
|
e = min(e, et)
|
|
else:
|
|
break
|
|
else:
|
|
ok = hit = True
|
|
r = b**e
|
|
il = b**-e
|
|
for _ in args:
|
|
_[1][-1] = _[1][-1]*il
|
|
break
|
|
if not ok:
|
|
break
|
|
else:
|
|
hit = True
|
|
lenn = len(n)
|
|
r = Mul(*n)
|
|
for _ in args:
|
|
_[1] = _[1][:len(_[1]) - lenn]
|
|
if hit:
|
|
mid = Add(*[Mul(*cc)*Mul(*nc) for cc, nc in args])
|
|
else:
|
|
mid = expr
|
|
|
|
from sympy.simplify.powsimp import powsimp
|
|
|
|
# sort the symbols so the Dummys would appear in the same
|
|
# order as the original symbols, otherwise you may introduce
|
|
# a factor of -1, e.g. A**2 - B**2) -- {A:y, B:x} --> y**2 - x**2
|
|
# and the former factors into two terms, (A - B)*(A + B) while the
|
|
# latter factors into 3 terms, (-1)*(x - y)*(x + y)
|
|
rep1 = [(n, Dummy()) for n in sorted(nc_symbols, key=default_sort_key)]
|
|
unrep1 = [(v, k) for k, v in rep1]
|
|
unrep1.reverse()
|
|
new_mid, r2, _ = _mask_nc(mid.subs(rep1))
|
|
new_mid = powsimp(factor(new_mid))
|
|
|
|
new_mid = new_mid.subs(r2).subs(unrep1)
|
|
|
|
if new_mid.is_Pow:
|
|
return _keep_coeff(c, g*l*new_mid*r)
|
|
|
|
if new_mid.is_Mul:
|
|
def _pemexpand(expr):
|
|
"Expand with the minimal set of hints necessary to check the result."
|
|
return expr.expand(deep=True, mul=True, power_exp=True,
|
|
power_base=False, basic=False, multinomial=True, log=False)
|
|
# XXX TODO there should be a way to inspect what order the terms
|
|
# must be in and just select the plausible ordering without
|
|
# checking permutations
|
|
cfac = []
|
|
ncfac = []
|
|
for f in new_mid.args:
|
|
if f.is_commutative:
|
|
cfac.append(f)
|
|
else:
|
|
b, e = f.as_base_exp()
|
|
if e.is_Integer:
|
|
ncfac.extend([b]*e)
|
|
else:
|
|
ncfac.append(f)
|
|
pre_mid = g*Mul(*cfac)*l
|
|
target = _pemexpand(expr/c)
|
|
for s in variations(ncfac, len(ncfac)):
|
|
ok = pre_mid*Mul(*s)*r
|
|
if _pemexpand(ok) == target:
|
|
return _keep_coeff(c, ok)
|
|
|
|
# mid was an Add that didn't factor successfully
|
|
return _keep_coeff(c, g*l*mid*r)
|