123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569 |
- """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)
|