def _matches(self, expr, repl_dict={}): # weed out negative one prefixes sign = 1 a, b = self.as_two_terms() if a is S.NegativeOne: if b.is_Mul: sign = -sign else: # the remainder, b, is not a Mul anymore return b.matches(-expr, repl_dict) expr = sympify(expr) if expr.is_Mul and expr.args[0] is S.NegativeOne: expr = -expr sign = -sign if not expr.is_Mul: # expr can only match if it matches b and a matches +/- 1 if len(self.args) == 2: # quickly test for equality if b == expr: return a.matches(Rational(sign), repl_dict) # do more expensive match dd = b.matches(expr, repl_dict) if dd is None: return None dd = a.matches(Rational(sign), dd) return dd return None d = repl_dict.copy() # weed out identical terms pp = list(self.args) ee = list(expr.args) for p in self.args: if p in expr.args: ee.remove(p) pp.remove(p) # only one symbol left in pattern -> match the remaining expression if len(pp) == 1 and isinstance(pp[0], C.Wild): if len(ee) == 1: d[pp[0]] = sign * ee[0] else: d[pp[0]] = sign * expr.func(*ee) return d if len(ee) != len(pp): return None for p, e in zip(pp, ee): d = p.xreplace(d).matches(e, d) if d is None: return None return d
def test_rational_is_integer(self): self.assertTrue(Rational.is_integer(F(-1, 1))) self.assertTrue(Rational.is_integer(F(0, 1))) self.assertTrue(Rational.is_integer(F(1, 1))) self.assertTrue(Rational.is_integer(F(42, 1))) self.assertTrue(Rational.is_integer(F(2, 2))) self.assertTrue(Rational.is_integer(F(8, 4))) self.assertFalse(Rational.is_integer(F(1, 2))) self.assertFalse(Rational.is_integer(F(1, 3))) self.assertFalse(Rational.is_integer(F(2, 3)))
def _eval_nseries(self, x, x0, n): assert len(self.args) == 1 arg = self.args[0] arg0 = arg.limit(x, 0) from sympy import oo if arg0 in [-oo, oo]: raise PoleError("Cannot expand around %s" % (arg)) if arg0 is not S.Zero: e = self.func(arg) e1 = e.expand() if e == e1: #for example when e = sin(x+1) or e = sin(cos(x)) #let's try the general algorithm term = e.subs(x, S.Zero) series = term fact = S.One for i in range(n - 1): i += 1 fact *= Rational(i) e = e.diff(x) term = e.subs(x, S.Zero) * (x**i) / fact term = term.expand() series += term return series + C.Order(x**n, x) return self.nseries(x, x0, n) l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, arg, g) g = g.nseries(x, x0, n) l.append(g) return Add(*l) + C.Order(x**n, x)
def _eval_nseries(self, x, n): """ This function does compute series for multivariate functions, but the expansion is always in terms of *one* variable. Examples: >>> from sympy import atan2, O >>> from sympy.abc import x, y >>> atan2(x, y).series(x, n=2) atan2(0, y) + x/y + O(x**2) >>> atan2(x, y).series(y, n=2) atan2(x, 0) - y/x + O(y**2) """ if self.func.nargs is None: raise NotImplementedError('series for user-defined \ functions are not supported.') args = self.args args0 = [t.limit(x, 0) for t in args] if any([t is S.NaN or t.is_bounded is False for t in args0]): raise PoleError("Cannot expand %s around 0" % (args)) if (self.func.nargs == 1 and args0[0]) or self.func.nargs > 1: e = self e1 = e.expand() if e == e1: #for example when e = sin(x+1) or e = sin(cos(x)) #let's try the general algorithm term = e.subs(x, S.Zero) if term.is_bounded is False or term is S.NaN: raise PoleError("Cannot expand %s around 0" % (self)) series = term fact = S.One for i in range(n - 1): i += 1 fact *= Rational(i) e = e.diff(x) subs = e.subs(x, S.Zero) if subs is S.NaN: # try to evaluate a limit if we have to subs = e.limit(x, S.Zero) if subs.is_bounded is False: raise PoleError("Cannot expand %s around 0" % (self)) term = subs * (x**i) / fact term = term.expand() series += term return series + C.Order(x**n, x) return e1.nseries(x, n=n) arg = self.args[0] l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, arg, g) g = g.nseries(x, n=n) l.append(g) return Add(*l) + C.Order(x**n, x)
def _eval_nseries(self, x, n): if self.func.nargs != 1: raise NotImplementedError('series for user-defined and \ multi-arg functions are not supported.') arg = self.args[0] arg0 = arg.limit(x, 0) from sympy import oo if arg0 == S.NaN or arg0.is_bounded == False: raise PoleError("Cannot expand %s around 0" % (arg)) if arg0: e = self e1 = e.expand() if e == e1: #for example when e = sin(x+1) or e = sin(cos(x)) #let's try the general algorithm term = e.subs(x, S.Zero) if arg0 == S.NaN or term.is_bounded == False: raise PoleError("Cannot expand %s around 0" % (self)) series = term fact = S.One for i in range(n - 1): i += 1 fact *= Rational(i) e = e.diff(x) subs = e.subs(x, S.Zero) if subs == S.NaN: # try to evaluate a limit if we have to subs = e.limit(x, S.Zero) if subs.is_bounded == False: raise PoleError("Cannot expand %s around 0" % (self)) term = subs * (x**i) / fact term = term.expand() series += term return series + C.Order(x**n, x) return e1.nseries(x, n=n) l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, arg, g) g = g.nseries(x, n=n) l.append(g) return Add(*l) + C.Order(x**n, x)
def _matches(self, expr, repl_dict={}, evaluate=False): if evaluate: return self.subs(repl_dict).matches(expr, repl_dict) # weed out negative one prefixes sign = 1 a, b = self.as_two_terms() if a is S.NegativeOne: if b.is_Mul: sign = -sign else: # the remainder, b, is not a Mul anymore return b.matches(-expr, repl_dict, evaluate) expr = sympify(expr) if expr.is_Mul and expr.args[0] is S.NegativeOne: expr = -expr; sign = -sign if not expr.is_Mul: # expr can only match if it matches b and a matches +/- 1 if len(self.args) == 2: # quickly test for equality if b == expr: return a.matches(Rational(sign), repl_dict, evaluate) # do more expensive match dd = b.matches(expr, repl_dict, evaluate) if dd == None: return None dd = a.matches(Rational(sign), dd, evaluate) return dd return None d = repl_dict.copy() # weed out identical terms pp = list(self.args) ee = list(expr.args) for p in self.args: if p in expr.args: ee.remove(p) pp.remove(p) # only one symbol left in pattern -> match the remaining expression if len(pp) == 1 and isinstance(pp[0], C.Wild): if len(ee) == 1: d[pp[0]] = sign * ee[0] else: d[pp[0]] = sign * (type(expr)(*ee)) return d if len(ee) != len(pp): return None i = 0 for p, e in zip(pp, ee): if i == 0 and sign != 1: try: e = sign * e except TypeError: return None d = p.matches(e, d, evaluate=not i) i += 1 if d is None: return None return d
def flatten(cls, seq): """Return commutative, noncommutative and order arguments by combining related terms. ** Developer Notes ** * In an expression like ``a*b*c``, python process this through sympy as ``Mul(Mul(a, b), c)``. This can have undesirable consequences. - Sometimes terms are not combined as one would like: {c.f. http://code.google.com/p/sympy/issues/detail?id=1497} >>> from sympy import Mul, sqrt >>> from sympy.abc import x, y, z >>> 2*(x + 1) # this is the 2-arg Mul behavior 2*x + 2 >>> y*(x + 1)*2 2*y*(x + 1) >>> 2*(x + 1)*y # 2-arg result will be obtained first y*(2*x + 2) >>> Mul(2, x + 1, y) # all 3 args simultaneously processed 2*y*(x + 1) >>> 2*((x + 1)*y) # parentheses can control this behavior 2*y*(x + 1) Powers with compound bases may not find a single base to combine with unless all arguments are processed at once. Post-processing may be necessary in such cases. {c.f. http://code.google.com/p/sympy/issues/detail?id=2629} >>> a = sqrt(x*sqrt(y)) >>> a**3 (x*sqrt(y))**(3/2) >>> Mul(a,a,a) (x*sqrt(y))**(3/2) >>> a*a*a x*sqrt(y)*sqrt(x*sqrt(y)) >>> _.subs(a.base, z).subs(z, a.base) (x*sqrt(y))**(3/2) - If more than two terms are being multiplied then all the previous terms will be re-processed for each new argument. So if each of ``a``, ``b`` and ``c`` were :class:`Mul` expression, then ``a*b*c`` (or building up the product with ``*=``) will process all the arguments of ``a`` and ``b`` twice: once when ``a*b`` is computed and again when ``c`` is multiplied. Using ``Mul(a, b, c)`` will process all arguments once. * The results of Mul are cached according to arguments, so flatten will only be called once for ``Mul(a, b, c)``. If you can structure a calculation so the arguments are most likely to be repeats then this can save time in computing the answer. For example, say you had a Mul, M, that you wished to divide by ``d[i]`` and multiply by ``n[i]`` and you suspect there are many repeats in ``n``. It would be better to compute ``M*n[i]/d[i]`` rather than ``M/d[i]*n[i]`` since every time n[i] is a repeat, the product, ``M*n[i]`` will be returned without flattening -- the cached value will be returned. If you divide by the ``d[i]`` first (and those are more unique than the ``n[i]``) then that will create a new Mul, ``M/d[i]`` the args of which will be traversed again when it is multiplied by ``n[i]``. {c.f. http://code.google.com/p/sympy/issues/detail?id=2607} This consideration is moot if the cache is turned off. NB The validity of the above notes depends on the implementation details of Mul and flatten which may change at any time. Therefore, you should only consider them when your code is highly performance sensitive. """ # apply associativity, separate commutative part of seq c_part = [] # out: commutative factors nc_part = [] # out: non-commutative factors nc_seq = [] coeff = S.One # standalone term # e.g. 3 * ... c_powers = [] # (base,exp) n # e.g. (x,n) for x num_exp = [] # (num-base, exp) y # e.g. (3, y) for ... * 3 * ... neg1e = 0 # exponent on -1 extracted from Number-based Pow pnum_rat = {} # (num-base, Rat-exp) 1/2 # e.g. (3, 1/2) for ... * 3 * ... order_symbols = None # --- PART 1 --- # # "collect powers and coeff": # # o coeff # o c_powers # o num_exp # o neg1e # o pnum_rat # # NOTE: this is optimized for all-objects-are-commutative case for o in seq: # O(x) if o.is_Order: o, order_symbols = o.as_expr_variables(order_symbols) # Mul([...]) if o.is_Mul: if o.is_commutative: seq.extend(o.args) # XXX zerocopy? else: # NCMul can have commutative parts as well for q in o.args: if q.is_commutative: seq.append(q) else: nc_seq.append(q) # append non-commutative marker, so we don't forget to # process scheduled non-commutative objects seq.append(NC_Marker) continue # 3 elif o.is_Number: if o is S.NaN or coeff is S.ComplexInfinity and o is S.Zero: # we know for sure the result will be nan return [S.NaN], [], None elif coeff.is_Number: # it could be zoo coeff *= o if coeff is S.NaN: # we know for sure the result will be nan return [S.NaN], [], None continue elif o is S.ComplexInfinity: if not coeff or coeff is S.ComplexInfinity: # we know for sure the result will be nan return [S.NaN], [], None coeff = S.ComplexInfinity continue elif o.is_commutative: # e # o = b b, e = o.as_base_exp() # y # 3 if o.is_Pow and b.is_Number: # get all the factors with numeric base so they can be # combined below, but don't combine negatives unless # the exponent is an integer if e.is_Rational: if e.is_Integer: coeff *= Pow(b, e) # it is an unevaluated power continue elif e.is_negative: # also a sign of an unevaluated power seq.append(Pow(b, e)) continue elif b.is_negative: neg1e += e b = -b if b is not S.One: pnum_rat.setdefault(b, []).append(e) continue elif b.is_positive or e.is_integer: num_exp.append((b, e)) continue c_powers.append((b,e)) # NON-COMMUTATIVE # TODO: Make non-commutative exponents not combine automatically else: if o is not NC_Marker: nc_seq.append(o) # process nc_seq (if any) while nc_seq: o = nc_seq.pop(0) if not nc_part: nc_part.append(o) continue # b c b+c # try to combine last terms: a * a -> a o1 = nc_part.pop() b1,e1 = o1.as_base_exp() b2,e2 = o.as_base_exp() new_exp = e1 + e2 # Only allow powers to combine if the new exponent is # not an Add. This allow things like a**2*b**3 == a**5 # if a.is_commutative == False, but prohibits # a**x*a**y and x**a*x**b from combining (x,y commute). if b1==b2 and (not new_exp.is_Add): o12 = b1 ** new_exp # now o12 could be a commutative object if o12.is_commutative: seq.append(o12) continue else: nc_seq.insert(0, o12) else: nc_part.append(o1) nc_part.append(o) # We do want a combined exponent if it would not be an Add, such as # y 2y 3y # x * x -> x # We determine this if two exponents have the same term in as_coeff_mul # # Unfortunately, this isn't smart enough to consider combining into # exponents that might already be adds, so things like: # z - y y # x * x will be left alone. This is because checking every possible # combination can slow things down. # gather exponents of common bases... # in c_powers new_c_powers = [] common_b = {} # b:e for b, e in c_powers: co = e.as_coeff_mul() common_b.setdefault(b, {}).setdefault(co[1], []).append(co[0]) for b, d in common_b.items(): for di, li in d.items(): d[di] = Add(*li) for b, e in common_b.items(): for t, c in e.items(): new_c_powers.append((b, c*Mul(*t))) c_powers = new_c_powers # and in num_exp new_num_exp = [] common_b = {} # b:e for b, e in num_exp: co = e.as_coeff_mul() common_b.setdefault(b, {}).setdefault(co[1], []).append(co[0]) for b, d in common_b.items(): for di, li in d.items(): d[di] = Add(*li) for b, e in common_b.items(): for t, c in e.items(): new_num_exp.append((b,c*Mul(*t))) num_exp = new_num_exp # --- PART 2 --- # # o process collected powers (x**0 -> 1; x**1 -> x; otherwise Pow) # o combine collected powers (2**x * 3**x -> 6**x) # with numeric base # ................................ # now we have: # - coeff: # - c_powers: (b, e) # - num_exp: (2, e) # - pnum_rat: {(1/3, [1/3, 2/3, 1/4])} # 0 1 # x -> 1 x -> x for b, e in c_powers: if e is S.One: if b.is_Number: coeff *= b else: c_part.append(b) elif not e is S.Zero: c_part.append(Pow(b, e)) # x x x # 2 * 3 -> 6 inv_exp_dict = {} # exp:Mul(num-bases) x x # e.g. x:6 for ... * 2 * 3 * ... for b, e in num_exp: inv_exp_dict.setdefault(e, []).append(b) for e, b in inv_exp_dict.items(): inv_exp_dict[e] = Mul(*b) c_part.extend([Pow(b, e) for e, b in inv_exp_dict.iteritems() if e]) # b, e -> e, b # {(1/5, [1/3]), (1/2, [1/12, 1/4]} -> {(1/3, [1/5, 1/2])} comb_e = {} for b, e in pnum_rat.iteritems(): comb_e.setdefault(Add(*e), []).append(b) del pnum_rat # process them, reducing exponents to values less than 1 # and updating coeff if necessary else adding them to # num_rat for further processing num_rat = [] for e, b in comb_e.iteritems(): b = Mul(*b) if e.q == 1: coeff *= Pow(b, e) continue if e.p > e.q: e_i, ep = divmod(e.p, e.q) coeff *= Pow(b, e_i) e = Rational(ep, e.q) num_rat.append((b, e)) del comb_e # extract gcd of bases in num_rat # 2**(1/3)*6**(1/4) -> 2**(1/3+1/4)*3**(1/4) pnew = {} i = 0 # steps through num_rat which may grow while i < len(num_rat): bi, ei = num_rat[i] grow = [] for j in range(i + 1, len(num_rat)): bj, ej = num_rat[j] g = igcd(bi, bj) if g != 1: # 4**r1*6**r2 -> 2**(r1+r2) * 2**r1 * 3**r2 # this might have a gcd with something else e = ei + ej if e.q == 1: coeff *= Pow(g, e) else: if e.p > e.q: e_i, ep = divmod(e.p, e.q) # change e in place coeff *= Pow(g, e_i) e = Rational(ep, e.q) grow.append((g, e)) # update the jth item num_rat[j] = (bj//g, ej) # update bi that we are checking with bi = bi//g if bi is S.One: break if bi is not S.One: obj = Pow(bi, ei) if obj.is_Number: coeff *= obj else: if obj.is_Mul: # sqrt(12) -> 2*sqrt(3) c, obj = obj.args # expecting only 2 args coeff *= c assert obj.is_Pow bi, ei = obj.args pnew.setdefault(ei, []).append(bi) num_rat.extend(grow) i += 1 # combine bases of the new powers for e, b in pnew.iteritems(): pnew[e] = Mul(*b) # see if there is a base with matching coefficient # that the -1 can be joined with if neg1e: p = Pow(S.NegativeOne, neg1e) if p.is_Number: coeff *= p else: if p.is_Mul: c, p = p.args coeff *= c assert p.is_Pow and p.base is S.NegativeOne neg1e = p.args[1] for e, b in pnew.iteritems(): if e == neg1e and b.is_positive: pnew[e] = -b break else: c_part.append(p) # add all the pnew powers c_part.extend([Pow(b, e) for e, b in pnew.iteritems()]) # oo, -oo if (coeff is S.Infinity) or (coeff is S.NegativeInfinity): new_c_part = [] coeff_sign = 1 for t in c_part: if t.is_positive: continue if t.is_negative: coeff_sign *= -1 continue new_c_part.append(t) c_part = new_c_part new_nc_part = [] for t in nc_part: if t.is_positive: continue if t.is_negative: coeff_sign *= -1 continue new_nc_part.append(t) nc_part = new_nc_part coeff *= coeff_sign # zoo if coeff is S.ComplexInfinity: # zoo might be # unbounded_real + bounded_im # bounded_real + unbounded_im # unbounded_real + unbounded_im # and non-zero real or imaginary will not change that status. c_part = [c for c in c_part if not (c.is_nonzero and c.is_real is not None)] nc_part = [c for c in nc_part if not (c.is_nonzero and c.is_real is not None)] # 0 elif coeff is S.Zero: # we know for sure the result will be 0 return [coeff], [], order_symbols # order commutative part canonically c_part.sort(key=cmp_to_key(Basic.compare)) # current code expects coeff to be always in slot-0 if coeff is not S.One: c_part.insert(0, coeff) # we are done if len(c_part)==2 and c_part[0].is_Number and c_part[1].is_Add: # 2*(1+a) -> 2 + 2 * a coeff = c_part[0] c_part = [Add(*[coeff*f for f in c_part[1].args])] return c_part, nc_part, order_symbols
def _rgcd(a, b): return Rational(Integer(igcd(a.p, b.p)), Integer(ilcm(a.q, b.q)))
def _eval_nseries(self, x, n, logx): """ This function does compute series for multivariate functions, but the expansion is always in terms of *one* variable. Examples: >>> from sympy import atan2, O >>> from sympy.abc import x, y >>> atan2(x, y).series(x, n=2) atan2(0, y) + x/y + O(x**2) >>> atan2(x, y).series(y, n=2) atan2(x, 0) - y/x + O(y**2) This function also computes asymptotic expansions, if necessary and possible: >>> from sympy import loggamma >>> loggamma(1/x)._eval_nseries(x,0,None) log(x)/2 - log(x)/x - 1/x + O(1) """ if self.func.nargs is None: raise NotImplementedError('series for user-defined \ functions are not supported.') args = self.args args0 = [t.limit(x, 0) for t in args] if any([t.is_bounded == False for t in args0]): from sympy import Dummy, oo, zoo, nan a = [t.compute_leading_term(x, logx=logx) for t in args] a0 = [t.limit(x, 0) for t in a] if any([t.has(oo, -oo, zoo, nan) for t in a0]): return self._eval_aseries(n, args0, x, logx)._eval_nseries(x, n, logx) # Careful: the argument goes to oo, but only logarithmically so. We # are supposed to do a power series expansion "around the # logarithmic term". e.g. # f(1+x+log(x)) # -> f(1+logx) + x*f'(1+logx) + O(x**2) # where 'logx' is given in the argument a = [t._eval_nseries(x, n, logx) for t in args] z = [r - r0 for (r, r0) in zip(a, a0)] p = [Dummy() for t in z] q = [] v = None w = None for ai, zi, pi in zip(a0, z, p): if zi.has(x): if v is not None: raise NotImplementedError q.append(ai + pi) v = pi w = zi else: q.append(ai) e1 = self.func(*q) if v is None: return e1 s = e1._eval_nseries(v, n, logx) o = s.getO() s = s.removeO() s = s.subs(v, zi).expand() + C.Order(o.expr.subs(v, zi), x) return s if (self.func.nargs == 1 and args0[0]) or self.func.nargs > 1: e = self e1 = e.expand() if e == e1: #for example when e = sin(x+1) or e = sin(cos(x)) #let's try the general algorithm term = e.subs(x, S.Zero) if term.is_bounded is False or term is S.NaN: raise PoleError("Cannot expand %s around 0" % (self)) series = term fact = S.One for i in range(n - 1): i += 1 fact *= Rational(i) e = e.diff(x) subs = e.subs(x, S.Zero) if subs is S.NaN: # try to evaluate a limit if we have to subs = e.limit(x, S.Zero) if subs.is_bounded is False: raise PoleError("Cannot expand %s around 0" % (self)) term = subs * (x**i) / fact term = term.expand() series += term return series + C.Order(x**n, x) return e1.nseries(x, n=n, logx=logx) arg = self.args[0] l = [] g = None for i in xrange(n + 2): g = self.taylor_term(i, arg, g) g = g.nseries(x, n=n, logx=logx) l.append(g) return Add(*l) + C.Order(x**n, x)
def flatten(cls, seq): # apply associativity, separate commutative part of seq c_part = [] # out: commutative factors nc_part = [] # out: non-commutative factors nc_seq = [] coeff = S.One # standalone term # e.g. 3 * ... c_powers = [] # (base,exp) n # e.g. (x,n) for x num_exp = [] # (num-base, exp) y # e.g. (3, y) for ... * 3 * ... neg1e = 0 # exponent on -1 extracted from Number-based Pow pnum_rat = {} # (num-base, Rat-exp) 1/2 # e.g. (3, 1/2) for ... * 3 * ... order_symbols = None # --- PART 1 --- # # "collect powers and coeff": # # o coeff # o c_powers # o num_exp # o neg1e # o pnum_rat # # NOTE: this is optimized for all-objects-are-commutative case for o in seq: # O(x) if o.is_Order: o, order_symbols = o.as_expr_variables(order_symbols) # Mul([...]) if o.is_Mul: if o.is_commutative: seq.extend(o.args) # XXX zerocopy? else: # NCMul can have commutative parts as well for q in o.args: if q.is_commutative: seq.append(q) else: nc_seq.append(q) # append non-commutative marker, so we don't forget to # process scheduled non-commutative objects seq.append(NC_Marker) continue # 3 elif o.is_Number: if o is S.NaN or coeff is S.ComplexInfinity and o is S.Zero: # we know for sure the result will be nan return [S.NaN], [], None elif coeff.is_Number: # it could be zoo coeff *= o if coeff is S.NaN: # we know for sure the result will be nan return [S.NaN], [], None continue elif o is S.ComplexInfinity: if not coeff or coeff is S.ComplexInfinity: # we know for sure the result will be nan return [S.NaN], [], None coeff = S.ComplexInfinity continue elif o.is_commutative: # e # o = b b, e = o.as_base_exp() # y # 3 if o.is_Pow and b.is_Number: # get all the factors with numeric base so they can be # combined below, but don't combine negatives unless # the exponent is an integer if e.is_Rational: if e.is_Integer: coeff *= Pow(b, e) # it is an unevaluated power continue elif e.is_negative: # also a sign of an unevaluated power seq.append(Pow(b, e)) continue elif b.is_negative: neg1e += e b = -b if b is not S.One: pnum_rat.setdefault(b, []).append(e) continue elif b.is_positive or e.is_integer: num_exp.append((b, e)) continue c_powers.append((b, e)) # NON-COMMUTATIVE # TODO: Make non-commutative exponents not combine automatically else: if o is not NC_Marker: nc_seq.append(o) # process nc_seq (if any) while nc_seq: o = nc_seq.pop(0) if not nc_part: nc_part.append(o) continue # b c b+c # try to combine last terms: a * a -> a o1 = nc_part.pop() b1, e1 = o1.as_base_exp() b2, e2 = o.as_base_exp() new_exp = e1 + e2 # Only allow powers to combine if the new exponent is # not an Add. This allow things like a**2*b**3 == a**5 # if a.is_commutative == False, but prohibits # a**x*a**y and x**a*x**b from combining (x,y commute). if b1 == b2 and (not new_exp.is_Add): o12 = b1**new_exp # now o12 could be a commutative object if o12.is_commutative: seq.append(o12) continue else: nc_seq.insert(0, o12) else: nc_part.append(o1) nc_part.append(o) # We do want a combined exponent if it would not be an Add, such as # y 2y 3y # x * x -> x # We determine this if two exponents have the same term in as_coeff_mul # # Unfortunately, this isn't smart enough to consider combining into # exponents that might already be adds, so things like: # z - y y # x * x will be left alone. This is because checking every possible # combination can slow things down. # gather exponents of common bases... # in c_powers new_c_powers = [] common_b = {} # b:e for b, e in c_powers: co = e.as_coeff_mul() common_b.setdefault(b, {}).setdefault(co[1], []).append(co[0]) for b, d in common_b.items(): for di, li in d.items(): d[di] = Add(*li) for b, e in common_b.items(): for t, c in e.items(): new_c_powers.append((b, c * Mul(*t))) c_powers = new_c_powers # and in num_exp new_num_exp = [] common_b = {} # b:e for b, e in num_exp: co = e.as_coeff_mul() common_b.setdefault(b, {}).setdefault(co[1], []).append(co[0]) for b, d in common_b.items(): for di, li in d.items(): d[di] = Add(*li) for b, e in common_b.items(): for t, c in e.items(): new_num_exp.append((b, c * Mul(*t))) num_exp = new_num_exp # --- PART 2 --- # # o process collected powers (x**0 -> 1; x**1 -> x; otherwise Pow) # o combine collected powers (2**x * 3**x -> 6**x) # with numeric base # ................................ # now we have: # - coeff: # - c_powers: (b, e) # - num_exp: (2, e) # - pnum_rat: {(1/3, [1/3, 2/3, 1/4])} # 0 1 # x -> 1 x -> x for b, e in c_powers: if e is S.One: if b.is_Number: coeff *= b else: c_part.append(b) elif not e is S.Zero: c_part.append(Pow(b, e)) # x x x # 2 * 3 -> 6 inv_exp_dict = {} # exp:Mul(num-bases) x x # e.g. x:6 for ... * 2 * 3 * ... for b, e in num_exp: inv_exp_dict.setdefault(e, []).append(b) for e, b in inv_exp_dict.items(): inv_exp_dict[e] = Mul(*b) c_part.extend([Pow(b, e) for e, b in inv_exp_dict.iteritems() if e]) # b, e -> e, b # {(1/5, [1/3]), (1/2, [1/12, 1/4]} -> {(1/3, [1/5, 1/2])} comb_e = {} for b, e in pnum_rat.iteritems(): comb_e.setdefault(Add(*e), []).append(b) del pnum_rat # process them, reducing exponents to values less than 1 # and updating coeff if necessary else adding them to # num_rat for further processing num_rat = [] for e, b in comb_e.iteritems(): b = Mul(*b) if e.q == 1: coeff *= Pow(b, e) continue if e.p > e.q: e_i, ep = divmod(e.p, e.q) coeff *= Pow(b, e_i) e = Rational(ep, e.q) num_rat.append((b, e)) del comb_e # extract gcd of bases in num_rat # 2**(1/3)*6**(1/4) -> 2**(1/3+1/4)*3**(1/4) pnew = {} i = 0 # steps through num_rat which may grow while i < len(num_rat): bi, ei = num_rat[i] grow = [] for j in range(i + 1, len(num_rat)): bj, ej = num_rat[j] g = igcd(bi, bj) if g != 1: # 4**r1*6**r2 -> 2**(r1+r2) * 2**r1 * 3**r2 # this might have a gcd with something else e = ei + ej if e.q == 1: coeff *= Pow(g, e) else: if e.p > e.q: e_i, ep = divmod(e.p, e.q) # change e in place coeff *= Pow(g, e_i) e = Rational(ep, e.q) grow.append((g, e)) # update the jth item num_rat[j] = (bj // g, ej) # update bi that we are checking with bi = bi // g if bi is S.One: break if bi is not S.One: obj = Pow(bi, ei) if obj.is_Number: coeff *= obj else: if obj.is_Mul: # 12**(1/2) -> 2*sqrt(3) c, obj = obj.args # expecting only 2 args coeff *= c assert obj.is_Pow bi, ei = obj.args pnew.setdefault(ei, []).append(bi) num_rat.extend(grow) i += 1 # combine bases of the new powers for e, b in pnew.iteritems(): pnew[e] = Mul(*b) # see if there is a base with matching coefficient # that the -1 can be joined with if neg1e: p = Pow(S.NegativeOne, neg1e) if p.is_Number: coeff *= p else: if p.is_Mul: c, p = p.args coeff *= c assert p.is_Pow and p.base is S.NegativeOne neg1e = p.args[1] for e, b in pnew.iteritems(): if e == neg1e and b.is_positive: pnew[e] = -b break else: c_part.append(p) # add all the pnew powers c_part.extend([Pow(b, e) for e, b in pnew.iteritems()]) # oo, -oo if (coeff is S.Infinity) or (coeff is S.NegativeInfinity): new_c_part = [] coeff_sign = 1 for t in c_part: if t.is_positive: continue if t.is_negative: coeff_sign *= -1 continue new_c_part.append(t) c_part = new_c_part new_nc_part = [] for t in nc_part: if t.is_positive: continue if t.is_negative: coeff_sign *= -1 continue new_nc_part.append(t) nc_part = new_nc_part coeff *= coeff_sign # zoo if coeff is S.ComplexInfinity: # zoo might be # unbounded_real + bounded_im # bounded_real + unbounded_im # unbounded_real + unbounded_im # and non-zero real or imaginary will not change that status. c_part = [ c for c in c_part if not (c.is_nonzero and c.is_real is not None) ] nc_part = [ c for c in nc_part if not (c.is_nonzero and c.is_real is not None) ] # 0 elif coeff is S.Zero: # we know for sure the result will be 0 return [coeff], [], order_symbols # order commutative part canonically c_part.sort(key=cmp_to_key(Basic.compare)) # current code expects coeff to be always in slot-0 if coeff is not S.One: c_part.insert(0, coeff) # we are done if len(c_part) == 2 and c_part[0].is_Number and c_part[1].is_Add: # 2*(1+a) -> 2 + 2 * a coeff = c_part[0] c_part = [Add(*[coeff * f for f in c_part[1].args])] return c_part, nc_part, order_symbols
def _matches(self, expr, repl_dict={}, evaluate=False): # weed out negative one prefixes sign = 1 if self.args[0] == -1: self = -self sign = -sign if expr.is_Mul and expr.args[0] == -1: expr = -expr sign = -sign if evaluate: return self.subs(repl_dict).matches(expr, repl_dict) expr = sympify(expr) if not isinstance(expr, self.__class__): # if we can omit the first factor, we can match it to sign * one if Mul(*self.args[1:]) == expr: return self.args[0].matches(Rational(sign), repl_dict, evaluate) # two-factor product: if the 2nd factor matches, the first part must be sign * one if len(self.args[:]) == 2: dd = self.args[1].matches(expr, repl_dict, evaluate) if dd == None: return None dd = self.args[0].matches(Rational(sign), dd, evaluate) return dd return None if len(self.args[:]) == 0: if self == expr: return repl_dict return None d = repl_dict.copy() # weed out identical terms pp = list(self.args) ee = list(expr.args) for p in self.args: if p in expr.args: ee.remove(p) pp.remove(p) # only one symbol left in pattern -> match the remaining expression from symbol import Wild if len(pp) == 1 and isinstance(pp[0], Wild): if len(ee) == 1: d[pp[0]] = sign * ee[0] else: d[pp[0]] = sign * (type(expr)(*ee)) return d if len(ee) != len(pp): return None i = 0 for p, e in zip(pp, ee): if i == 0 and sign != 1: try: e = sign * e except TypeError: return None d = p.matches(e, d, evaluate=not i) i += 1 if d is None: return None return d