|
"""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 |
|
|
|
|
|
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 |
|
|
|
|
|
c, a = self.as_coeff_Add() |
|
v = None |
|
if not a.is_polynomial(): |
|
|
|
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: |
|
|
|
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)) |
|
e = exp.p |
|
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: |
|
|
|
|
|
tail = _keep_coeff(Rational(1, exp.q), tail) |
|
base, e = Pow(base, tail), exp.p |
|
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): |
|
"""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: |
|
|
|
|
|
|
|
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()) |
|
|
|
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() |
|
|
|
|
|
|
|
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]: |
|
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): |
|
keys = tuple(ordered(self.factors.keys())) |
|
values = [self.factors[k] for k in keys] |
|
return hash((keys, values)) |
|
|
|
def __repr__(self): |
|
return "Factors({%s})" % ', '.join( |
|
['%s: %s' % (k, v) for k, v in ordered(self.factors.items())]) |
|
|
|
@property |
|
def is_zero(self): |
|
""" |
|
>>> 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): |
|
""" |
|
>>> from sympy.core.exprtools import Factors |
|
>>> Factors(1).is_one |
|
True |
|
""" |
|
return not self.factors |
|
|
|
def as_expr(self): |
|
"""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): |
|
"""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: |
|
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): |
|
"""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: |
|
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): |
|
"""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): |
|
"""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): |
|
"""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): |
|
"""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): |
|
"""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): |
|
return self.mul(other) |
|
|
|
def __divmod__(self, other): |
|
return self.div(other) |
|
|
|
def __truediv__(self, other): |
|
return self.quo(other) |
|
|
|
def __mod__(self, other): |
|
return self.rem(other) |
|
|
|
def __pow__(self, other): |
|
return self.pow(other) |
|
|
|
def __eq__(self, other): |
|
if not isinstance(other, Factors): |
|
other = Factors(other) |
|
return self.factors == other.factors |
|
|
|
def __ne__(self, other): |
|
return not self == other |
|
|
|
|
|
class Term: |
|
"""Efficient representation of ``coeff*(numer/denom)``. """ |
|
|
|
__slots__ = ('coeff', 'numer', 'denom') |
|
|
|
def __init__(self, term, numer=None, denom=None): |
|
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): |
|
return hash((self.coeff, self.numer, self.denom)) |
|
|
|
def __repr__(self): |
|
return "Term(%s, %s, %s)" % (self.coeff, self.numer, self.denom) |
|
|
|
def as_expr(self): |
|
return self.coeff*(self.numer.as_expr()/self.denom.as_expr()) |
|
|
|
def mul(self, other): |
|
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): |
|
return Term(1/self.coeff, self.denom, self.numer) |
|
|
|
def quo(self, other): |
|
return self.mul(other.inv()) |
|
|
|
def pow(self, other): |
|
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): |
|
return Term(self.coeff.gcd(other.coeff), |
|
self.numer.gcd(other.numer), |
|
self.denom.gcd(other.denom)) |
|
|
|
def lcm(self, other): |
|
return Term(self.coeff.lcm(other.coeff), |
|
self.numer.lcm(other.numer), |
|
self.denom.lcm(other.denom)) |
|
|
|
def __mul__(self, other): |
|
if isinstance(other, Term): |
|
return self.mul(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __truediv__(self, other): |
|
if isinstance(other, Term): |
|
return self.quo(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __pow__(self, other): |
|
if isinstance(other, SYMPY_INTS): |
|
return self.pow(other) |
|
else: |
|
return NotImplemented |
|
|
|
def __eq__(self, other): |
|
return (self.coeff == other.coeff and |
|
self.numer == other.numer and |
|
self.denom == other.denom) |
|
|
|
def __ne__(self, other): |
|
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])) |
|
|
|
|
|
|
|
|
|
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: |
|
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): |
|
|
|
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 |
|
|
|
|
|
wrt = {i.args[0] for i in limits} |
|
|
|
|
|
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)] |
|
|
|
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] |
|
|
|
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 |
|
|
|
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' |
|
|
|
|
|
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, {}, [] |
|
|
|
|
|
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 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())) |
|
|
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
for i, a in enumerate(args): |
|
if i == 0: |
|
n = a[1][:] |
|
else: |
|
n = common_prefix(n, a[1]) |
|
if not n: |
|
|
|
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:] |
|
|
|
for i, a in enumerate(args): |
|
if i == 0: |
|
n = a[1][:] |
|
else: |
|
n = common_suffix(n, a[1]) |
|
if not n: |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
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) |
|
|
|
|
|
return _keep_coeff(c, g*l*mid*r) |
|
|