def _separatevars(expr, force): if len(expr.free_symbols) == 1: return expr # don't destroy a Mul since much of the work may already be done if expr.is_Mul: args = list(expr.args) changed = False for i, a in enumerate(args): args[i] = separatevars(a, force) changed = changed or args[i] != a if changed: expr = expr.func(*args) return expr # get a Pow ready for expansion if expr.is_Pow: expr = Pow(separatevars(expr.base, force=force), expr.exp) # First try other expansion methods expr = expr.expand(mul=False, multinomial=False, force=force) _expr, reps = posify(expr) if force else (expr, {}) expr = factor(_expr).subs(reps) if not expr.is_Add: return expr # Find any common coefficients to pull out args = list(expr.args) commonc = args[0].args_cnc(cset=True, warn=False)[0] for i in args[1:]: commonc &= i.args_cnc(cset=True, warn=False)[0] commonc = Mul(*commonc) commonc = commonc.as_coeff_Mul()[1] # ignore constants commonc_set = commonc.args_cnc(cset=True, warn=False)[0] # remove them for i, a in enumerate(args): c, nc = a.args_cnc(cset=True, warn=False) c = c - commonc_set args[i] = Mul(*c)*Mul(*nc) nonsepar = Add(*args) if len(nonsepar.free_symbols) > 1: _expr = nonsepar _expr, reps = posify(_expr) if force else (_expr, {}) _expr = (factor(_expr)).subs(reps) if not _expr.is_Add: nonsepar = _expr return commonc*nonsepar
def _sqrt_match(p): """Return [a, b, r] for p.match(a + b*sqrt(r)) where, in addition to matching, sqrt(r) also has then maximal sqrt_depth among addends of p. Examples ======== >>> from sympy.functions.elementary.miscellaneous import sqrt >>> from sympy.simplify.sqrtdenest import _sqrt_match >>> _sqrt_match(1 + sqrt(2) + sqrt(2)*sqrt(3) + 2*sqrt(1+sqrt(5))) [1 + sqrt(2) + sqrt(6), 2, 1 + sqrt(5)] """ p = _mexpand(p) if p.is_Number: res = (p, S.Zero, S.Zero) elif p.is_Add: pargs = list(p.args) # to make the process canonical, the argument is included in the tuple # so when the max is selected, it will be the largest arg having a # given depth v = [(sqrt_depth(x), x, i) for i, x in enumerate(pargs)] nmax = max(v) if nmax[0] == 0: res = [] else: depth, _, i = nmax r = pargs.pop(i) a = Add._from_args(pargs) b = S.One if r.is_Mul: bv = [] rv = [] for x in r.args: if sqrt_depth(x) < depth: bv.append(x) else: rv.append(x) b = Mul._from_args(bv) r = Mul._from_args(rv) res = (a, b, r**2) else: b, r = p.as_coeff_Mul() if is_sqrt(r): res = (S.Zero, b, r**2) else: res = [] return list(res)
def _entry(self, i, j, expand=True): from sympy import Dummy, Sum, Mul, ImmutableMatrix, Integer coeff, matrices = self.as_coeff_matrices() if len(matrices) == 1: # situation like 2*X, matmul is just X return coeff * matrices[0][i, j] indices = [None]*(len(matrices) + 1) ind_ranges = [None]*(len(matrices) - 1) indices[0] = i indices[-1] = j for i in range(1, len(matrices)): indices[i] = Dummy("i_%i" % i) for i, arg in enumerate(matrices[:-1]): ind_ranges[i] = arg.shape[1] - 1 matrices = [arg[indices[i], indices[i+1]] for i, arg in enumerate(matrices)] expr_in_sum = Mul.fromiter(matrices) if any(v.has(ImmutableMatrix) for v in matrices): expand = True result = coeff*Sum( expr_in_sum, *zip(indices[1:-1], [0]*len(ind_ranges), ind_ranges) ) # Don't waste time in result.doit() if the sum bounds are symbolic if not any(isinstance(v, (Integer, int)) for v in ind_ranges): expand = False return result.doit() if expand else result
def _separatevars_dict(expr, symbols): if symbols: if not all((t.is_Atom for t in symbols)): raise ValueError("symbols must be Atoms.") symbols = list(symbols) elif symbols is None: return {'coeff': expr} else: symbols = list(expr.free_symbols) if not symbols: return None ret = dict(((i, []) for i in symbols + ['coeff'])) for i in Mul.make_args(expr): expsym = i.free_symbols intersection = set(symbols).intersection(expsym) if len(intersection) > 1: return None if len(intersection) == 0: # There are no symbols, so it is part of the coefficient ret['coeff'].append(i) else: ret[intersection.pop()].append(i) # rebuild for k, v in ret.items(): ret[k] = Mul(*v) return ret
def product_simplify(s): """Main function for Product simplification""" from sympy.concrete.products import Product terms = Mul.make_args(s) p_t = [] # Product Terms o_t = [] # Other Terms for term in terms: if isinstance(term, Product): p_t.append(term) else: o_t.append(term) used = [False] * len(p_t) for method in range(2): for i, p_term1 in enumerate(p_t): if not used[i]: for j, p_term2 in enumerate(p_t): if not used[j] and i != j: if isinstance(product_mul(p_term1, p_term2, method), Product): p_t[i] = product_mul(p_term1, p_term2, method) used[j] = True result = Mul(*o_t) for i, p_term in enumerate(p_t): if not used[i]: result = Mul(result, p_term) return result
def _sqrt_symbolic_denest(a, b, r): """Given an expression, sqrt(a + b*sqrt(b)), return the denested expression or None. Algorithm: If r = ra + rb*sqrt(rr), try replacing sqrt(rr) in ``a`` with (y**2 - ra)/rb, and if the result is a quadratic, ca*y**2 + cb*y + cc, and (cb + b)**2 - 4*ca*cc is 0, then sqrt(a + b*sqrt(r)) can be rewritten as sqrt(ca*(sqrt(r) + (cb + b)/(2*ca))**2). Examples ======== >>> from sympy.simplify.sqrtdenest import _sqrt_symbolic_denest, sqrtdenest >>> from sympy import sqrt, Symbol >>> from sympy.abc import x >>> a, b, r = 16 - 2*sqrt(29), 2, -10*sqrt(29) + 55 >>> _sqrt_symbolic_denest(a, b, r) sqrt(-2*sqrt(29) + 11) + sqrt(5) If the expression is numeric, it will be simplified: >>> w = sqrt(sqrt(sqrt(3) + 1) + 1) + 1 + sqrt(2) >>> sqrtdenest(sqrt((w**2).expand())) 1 + sqrt(2) + sqrt(1 + sqrt(1 + sqrt(3))) Otherwise, it will only be simplified if assumptions allow: >>> w = w.subs(sqrt(3), sqrt(x + 3)) >>> sqrtdenest(sqrt((w**2).expand())) sqrt((sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2))**2) Notice that the argument of the sqrt is a square. If x is made positive then the sqrt of the square is resolved: >>> _.subs(x, Symbol('x', positive=True)) sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2) """ a, b, r = map(sympify, (a, b, r)) rval = _sqrt_match(r) if not rval: return None ra, rb, rr = rval if rb: y = Dummy('y', positive=True) try: newa = Poly(a.subs(sqrt(rr), (y**2 - ra)/rb), y) except PolynomialError: return None if newa.degree() == 2: ca, cb, cc = newa.all_coeffs() cb += b if _mexpand(cb**2 - 4*ca*cc).equals(0): z = sqrt(ca*(sqrt(r) + cb/(2*ca))**2) if z.is_number: z = _mexpand(Mul._from_args(z.as_content_primitive())) return z
def eval(cls, arg): if not arg.is_Atom: c, arg_ = factor_terms(arg).as_coeff_Mul() if arg_.is_Mul: arg_ = Mul(*[a if (sign(a) not in (-1, 1)) else sign(a) for a in arg_.args]) arg_ = sign(c)*arg_ else: arg_ = arg if arg_.atoms(AppliedUndef): return x, y = re(arg_), im(arg_) rv = atan2(y, x) if rv.is_number: return rv if arg_ != arg: return cls(arg_, evaluate=False)
def sub_pre(e): """ Replace y - x with -(x - y) if -1 can be extracted from y - x. """ reps = [a for a in e.atoms(Add) if a.could_extract_minus_sign()] # make it canonical reps.sort(key=default_sort_key) e = e.xreplace(dict((a, Mul._from_args([S.NegativeOne, -a])) for a in reps)) # repeat again for persisting Adds but mark these with a leading 1, -1 # e.g. y - x -> 1*-1*(x - y) if isinstance(e, Basic): negs = {} for a in sorted(e.atoms(Add), key=default_sort_key): if a in reps or a.could_extract_minus_sign(): negs[a] = Mul._from_args([S.One, S.NegativeOne, -a]) e = e.xreplace(negs) return e
def doit(self, **hints): """Evaluates limit""" e, z, z0, dir = self.args if hints.get('deep', True): e = e.doit(**hints) z = z.doit(**hints) z0 = z0.doit(**hints) if e == z: return z0 if not e.has(z): return e # gruntz fails on factorials but works with the gamma function # If no factorial term is present, e should remain unchanged. # factorial is defined to be zero for negative inputs (which # differs from gamma) so only rewrite for positive z0. if z0.is_positive: e = e.rewrite(factorial, gamma) if e.is_Mul: if abs(z0) is S.Infinity: # XXX todo: this should probably be stated in the # negative -- i.e. to exclude expressions that should # not be handled this way but I'm not sure what that # condition is; when ok is True it means that the leading # term approach is going to succeed (hopefully) ok = lambda w: (z in w.free_symbols and any(a.is_polynomial(z) or any(z in m.free_symbols and m.is_polynomial(z) for m in Mul.make_args(a)) for a in Add.make_args(w))) if all(ok(w) for w in e.as_numer_denom()): u = Dummy(positive=(z0 is S.Infinity)) inve = e.subs(z, 1/u) r = limit(inve.as_leading_term(u), u, S.Zero, "+" if z0 is S.Infinity else "-") if isinstance(r, Limit): return self else: return r if e.is_Order: return Order(limit(e.expr, z, z0), *e.args[1:]) try: r = gruntz(e, z, z0, dir) if r is S.NaN: raise PoleError() except (PoleError, ValueError): r = heuristics(e, z, z0, dir) if r is None: return self return r
def eval(cls, arg): if isinstance(arg, exp_polar): return periodic_argument(arg, oo) if not arg.is_Atom: c, arg_ = factor_terms(arg).as_coeff_Mul() if arg_.is_Mul: arg_ = Mul(*[a if (sign(a) not in (-1, 1)) else sign(a) for a in arg_.args]) arg_ = sign(c)*arg_ else: arg_ = arg if arg_.atoms(AppliedUndef): return x, y = arg_.as_real_imag() rv = atan2(y, x) if rv.is_number: return rv if arg_ != arg: return cls(arg_, evaluate=False)
def _print_Mul(self, expr): prec = precedence(expr) c, e = expr.as_coeff_Mul() if c < 0: expr = _keep_coeff(-c, e) sign = "-" else: sign = "" a = [] # items in the numerator b = [] # items that are in the denominator (if any) pow_paren = [] # Will collect all pow with more than one base element and exp = -1 if self.order not in ('old', 'none'): args = expr.as_ordered_factors() else: # use make_args in case expr was something like -x -> x args = Mul.make_args(expr) # Gather args for numerator/denominator for item in args: if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative: if item.exp != -1: b.append(Pow(item.base, -item.exp, evaluate=False)) else: if len(item.args[0].args) != 1 and isinstance(item.base, Mul): # To avoid situations like #14160 pow_paren.append(item) b.append(Pow(item.base, -item.exp)) elif item.is_Rational and item is not S.Infinity: if item.p != 1: a.append(Rational(item.p)) if item.q != 1: b.append(Rational(item.q)) else: a.append(item) a = a or [S.One] a_str = [self.parenthesize(x, prec, strict=False) for x in a] b_str = [self.parenthesize(x, prec, strict=False) for x in b] # To parenthesize Pow with exp = -1 and having more than one Symbol for item in pow_paren: if item.base in b: b_str[b.index(item.base)] = "(%s)" % b_str[b.index(item.base)] if len(b) == 0: return sign + '*'.join(a_str) elif len(b) == 1: return sign + '*'.join(a_str) + "/" + b_str[0] else: return sign + '*'.join(a_str) + "/(%s)" % '*'.join(b_str)
def futrig(e, **kwargs): """Return simplified ``e`` using Fu-like transformations. This is not the "Fu" algorithm. This is called by default from ``trigsimp``. By default, hyperbolics subexpressions will be simplified, but this can be disabled by setting ``hyper=False``. Examples ======== >>> from sympy import trigsimp, tan, sinh, tanh >>> from sympy.simplify.trigsimp import futrig >>> from sympy.abc import x >>> trigsimp(1/tan(x)**2) tan(x)**(-2) >>> futrig(sinh(x)/tanh(x)) cosh(x) """ from sympy.simplify.fu import hyper_as_trig from sympy.simplify.simplify import bottom_up e = sympify(e) if not isinstance(e, Basic): return e if not e.args: return e old = e e = bottom_up(e, lambda x: _futrig(x, **kwargs)) if kwargs.pop('hyper', True) and e.has(HyperbolicFunction): e, f = hyper_as_trig(e) e = f(_futrig(e)) if e != old and e.is_Mul and e.args[0].is_Rational: # redistribute leading coeff on 2-arg Add e = Mul(*e.as_coeff_Mul()) return e
def as_terms(self): """Transform an expression to a list of terms. """ from sympy.core import Add, Mul, S from sympy.core.exprtools import decompose_power gens, terms = set([]), [] for term in Add.make_args(self): coeff, _term = term.as_coeff_Mul() coeff = complex(coeff) cpart, ncpart = {}, [] if _term is not S.One: for factor in Mul.make_args(_term): if factor.is_number: try: coeff *= complex(factor) except ValueError: pass else: continue if factor.is_commutative: base, exp = decompose_power(factor) cpart[base] = exp gens.add(base) else: ncpart.append(factor) coeff = coeff.real, coeff.imag ncpart = tuple(ncpart) terms.append((term, (coeff, cpart, ncpart))) gens = sorted(gens, key=Basic.sorted_key) k, indices = len(gens), {} for i, g in enumerate(gens): indices[g] = i result = [] for term, (coeff, cpart, ncpart) in terms: monom = [0]*k for base, exp in cpart.iteritems(): monom[indices[base]] = exp result.append((term, (coeff, tuple(monom), ncpart))) return result, gens
def extract_commutative(kron): c_part = [] nc_part = [] for arg in kron.args: c, nc = arg.args_cnc() c_part.extend(c) nc_part.append(Mul._from_args(nc)) c_part = Mul(*c_part) if c_part != 1: return c_part*KroneckerProduct(*nc_part) return kron
def sub_post(e): """ Replace 1*-1*x with -x. """ replacements = [] for node in preorder_traversal(e): if isinstance(node, Mul) and \ node.args[0] is S.One and node.args[1] is S.NegativeOne: replacements.append((node, -Mul._from_args(node.args[2:]))) for node, replacement in replacements: e = e.xreplace({node: replacement}) return e
def contract_one_dims(parts): if len(parts) == 1: return parts[0] else: p1, p2 = parts[:2] if p2.is_Matrix: p2 = p2.T pbase = p1*p2 if len(parts) == 2: return pbase else: # len(parts) > 2 if pbase.is_Matrix: raise ValueError("") return pbase*Mul.fromiter(parts[2:])
def _parallel_dict_from_expr_if_gens(exprs, opt): """Transform expressions into a multinomial form given generators. """ k, indices = len(opt.gens), {} for i, g in enumerate(opt.gens): indices[g] = i polys = [] for expr in exprs: poly = {} if expr.is_Equality: expr = expr.lhs - expr.rhs for term in Add.make_args(expr): coeff, monom = [], [0]*k for factor in Mul.make_args(term): if not _not_a_coeff(factor) and factor.is_Number: coeff.append(factor) else: try: if opt.series is False: base, exp = decompose_power(factor) if exp < 0: exp, base = -exp, Pow(base, -S.One) else: base, exp = decompose_power_rat(factor) monom[indices[base]] = exp except KeyError: if not factor.free_symbols.intersection(opt.gens): coeff.append(factor) else: raise PolynomialError("%s contains an element of " "the set of generators." % factor) monom = tuple(monom) if monom in poly: poly[monom] += Mul(*coeff) else: poly[monom] = Mul(*coeff) polys.append(poly) return polys, opt.gens
def _print_Mul(self, expr): prec = precedence(expr) c, e = expr.as_coeff_Mul() if c < 0: expr = _keep_coeff(-c, e) sign = "-" else: sign = "" a = [] # items in the numerator b = [] # items that are in the denominator (if any) if self.order not in ('old', 'none'): args = expr.as_ordered_factors() else: # use make_args in case expr was something like -x -> x args = Mul.make_args(expr) # Gather args for numerator/denominator for item in args: if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative: if item.exp != -1: b.append(Pow(item.base, -item.exp, evaluate=False)) else: b.append(Pow(item.base, -item.exp)) elif item.is_Rational and item is not S.Infinity: if item.p != 1: a.append(Rational(item.p)) if item.q != 1: b.append(Rational(item.q)) else: a.append(item) a = a or [S.One] a_str = list(map(lambda x: self.parenthesize(x, prec), a)) b_str = list(map(lambda x: self.parenthesize(x, prec), b)) if len(b) == 0: return sign + '*'.join(a_str) elif len(b) == 1: if len(a) == 1 and not (a[0].is_Atom or a[0].is_Add): return sign + "%s/" % a_str[0] + '*'.join(b_str) else: return sign + '*'.join(a_str) + "/%s" % b_str[0] else: return sign + '*'.join(a_str) + "/(%s)" % '*'.join(b_str)
def _print_Mul(self, expr): prec = precedence(expr) c, e = expr.as_coeff_Mul() if c < 0: expr = _keep_coeff(-c, e) sign = "-" else: sign = "" a = [] # items in the numerator b = [] # items that are in the denominator (if any) if self.order not in ("old", "none"): args = expr.as_ordered_factors() else: # use make_args in case expr was something like -x -> x args = Mul.make_args(expr) # Gather args for numerator/denominator for item in args: if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative: if item.exp != -1: b.append(Pow(item.base, -item.exp, evaluate=False)) else: b.append(Pow(item.base, -item.exp)) elif item.is_Rational and item is not S.Infinity: if item.p != 1: a.append(Rational(item.p)) if item.q != 1: b.append(Rational(item.q)) else: a.append(item) a = a or [S.One] a_str = [self.parenthesize(x, prec) for x in a] b_str = [self.parenthesize(x, prec) for x in b] if len(b) == 0: return sign + "*".join(a_str) elif len(b) == 1: return sign + "*".join(a_str) + "/" + b_str[0] else: return sign + "*".join(a_str) + "/(%s)" % "*".join(b_str)
def update(b): '''Decide what to do with base, b. If its exponent is now an integer multiple of the Rational denominator, then remove it and put the factors of its base in the common_b dictionary or update the existing bases if necessary. If it has been zeroed out, simply remove the base. ''' newe, r = divmod(common_b[b], b[1]) if not r: common_b.pop(b) if newe: for m in Mul.make_args(b[0]**newe): b, e = bkey(m) if b not in common_b: common_b[b] = 0 common_b[b] += e if b[1] != 1: bases.append(b)
def _parallel_dict_from_expr_if_gens(exprs, opt): """Transform expressions into a multinomial form given generators. """ k, indices = len(opt.gens), {} for i, g in enumerate(opt.gens): indices[g] = i polys = [] for expr in exprs: poly = {} for term in Add.make_args(expr): coeff, monom = [], [0]*k for factor in Mul.make_args(term): if factor.is_Number: coeff.append(factor) else: try: base, exp = decompose_power(factor) if exp < 0: exp, base = -exp, Pow(base, -S.One) monom[indices[base]] = exp except KeyError: if not factor.has(*opt.gens): coeff.append(factor) else: raise PolynomialError("%s contains an element of the generators set" % factor) monom = tuple(monom) if monom in poly: poly[monom] += Mul(*coeff) else: poly[monom] = Mul(*coeff) polys.append(poly) return polys, opt.gens
def sum_simplify(s): """Main function for Sum simplification""" from sympy.concrete.summations import Sum from sympy.core.function import expand terms = Add.make_args(expand(s)) s_t = [] # Sum Terms o_t = [] # Other Terms for term in terms: if isinstance(term, Mul): other = 1 sum_terms = [] if not term.has(Sum): o_t.append(term) continue mul_terms = Mul.make_args(term) for mul_term in mul_terms: if isinstance(mul_term, Sum): r = mul_term._eval_simplify() sum_terms.extend(Add.make_args(r)) else: other = other * mul_term if len(sum_terms): #some simplification may have happened #use if so s_t.append(Mul(*sum_terms) * other) else: o_t.append(other) elif isinstance(term, Sum): #as above, we need to turn this into an add list r = term._eval_simplify() s_t.extend(Add.make_args(r)) else: o_t.append(term) result = Add(sum_combine(s_t), *o_t) return result
def __new__(cls, *args): # Check that the shape of the args is consistent matrices = [arg for arg in args if arg.is_Matrix] for i in range(len(matrices) - 1): A, B = matrices[i:i + 2] if A.cols != B.rows: raise ShapeError("Matrices %s and %s are not aligned" % (A, B)) if any(arg.is_zero for arg in args): return ZeroMatrix(matrices[0].rows, matrices[-1].cols) expr = matrixify(Mul.__new__(cls, *args)) if expr.is_Add: return MatAdd(*expr.args) if expr.is_Pow: assert expr.exp.is_Integer expr = Basic.__new__(MatMul, *[expr.base for i in range(expr.exp)]) if not expr.is_Mul: return expr if any(arg.is_Matrix and arg.is_ZeroMatrix for arg in expr.args): return ZeroMatrix(*expr.shape) # Clear out Identities nonmats = [M for M in expr.args if not M.is_Matrix] # scalars mats = [M for M in expr.args if M.is_Matrix] # matrices if any(M.is_Identity for M in mats): # Any identities around? newmats = [M for M in mats if not M.is_Identity] # clear out if len(newmats) == 0: # Did we lose everything? newmats = [Identity(expr.rows)] # put just one back in if mats != newmats: # Removed some I's but not everything? return MatMul(*(nonmats + newmats)) # Repeat with simpler expr return expr
def eval(cls, arg): from sympy.simplify.simplify import signsimp from sympy.core.basic import Atom from sympy.core.function import expand_mul if hasattr(arg, '_eval_Abs'): obj = arg._eval_Abs() if obj is not None: return obj if not isinstance(arg, Expr): raise TypeError("Bad argument type for Abs(): %s" % type(arg)) # handle what we can arg = signsimp(arg, evaluate=False) if arg.is_Mul: known = [] unk = [] for t in Mul.make_args(arg): tnew = cls(t) if tnew.func is cls: unk.append(tnew.args[0]) else: known.append(tnew) known = Mul(*known) unk = cls(Mul(*unk), evaluate=False) if unk else S.One return known * unk if arg is S.NaN: return S.NaN if arg.is_Pow: base, exponent = arg.as_base_exp() if base.is_real: if exponent.is_integer: if exponent.is_even: return arg if base is S.NegativeOne: return S.One if base.func is cls and exponent is S.NegativeOne: return arg return Abs(base)**exponent if base.is_positive == True: return base**re(exponent) return (-base)**re(exponent) * exp(-S.Pi * im(exponent)) if isinstance(arg, exp): return exp(re(arg.args[0])) if isinstance(arg, AppliedUndef): return if arg.is_Add and arg.has(S.Infinity, S.NegativeInfinity): if any(a.is_infinite for a in arg.as_real_imag()): return S.Infinity if arg.is_zero: return S.Zero if arg.is_nonnegative: return arg if arg.is_nonpositive: return -arg if arg.is_imaginary: arg2 = -S.ImaginaryUnit * arg if arg2.is_nonnegative: return arg2 # reject result if all new conjugates are just wrappers around # an expression that was already in the arg conj = arg.conjugate() new_conj = conj.atoms(conjugate) - arg.atoms(conjugate) if new_conj and all(arg.has(i.args[0]) for i in new_conj): return if arg != conj and arg != -conj: ignore = arg.atoms(Abs) abs_free_arg = arg.xreplace( dict([(i, Dummy(real=True)) for i in ignore])) unk = [a for a in abs_free_arg.free_symbols if a.is_real is None] if not unk or not all(conj.has(conjugate(u)) for u in unk): return sqrt(expand_mul(arg * conj))
def recurse_expr(expr, index_ranges={}): if expr.is_Mul: nonmatargs = [] pos_arg = [] pos_ind = [] dlinks = {} link_ind = [] counter = 0 args_ind = [] for arg in expr.args: retvals = recurse_expr(arg, index_ranges) assert isinstance(retvals, list) if isinstance(retvals, list): for i in retvals: args_ind.append(i) else: args_ind.append(retvals) for arg_symbol, arg_indices in args_ind: if arg_indices is None: nonmatargs.append(arg_symbol) continue if isinstance(arg_symbol, MatrixElement): arg_symbol = arg_symbol.args[0] pos_arg.append(arg_symbol) pos_ind.append(arg_indices) link_ind.append([None] * len(arg_indices)) for i, ind in enumerate(arg_indices): if ind in dlinks: other_i = dlinks[ind] link_ind[counter][i] = other_i link_ind[other_i[0]][other_i[1]] = (counter, i) dlinks[ind] = (counter, i) counter += 1 counter2 = 0 lines = {} while counter2 < len(link_ind): for i, e in enumerate(link_ind): if None in e: line_start_index = (i, e.index(None)) break cur_ind_pos = line_start_index cur_line = [] index1 = pos_ind[cur_ind_pos[0]][cur_ind_pos[1]] while True: d, r = cur_ind_pos if pos_arg[d] != 1: if r % 2 == 1: cur_line.append(transpose(pos_arg[d])) else: cur_line.append(pos_arg[d]) next_ind_pos = link_ind[d][1 - r] counter2 += 1 # Mark as visited, there will be no `None` anymore: link_ind[d] = (-1, -1) if next_ind_pos is None: index2 = pos_ind[d][1 - r] lines[(index1, index2)] = cur_line break cur_ind_pos = next_ind_pos lines = { k: MatMul.fromiter(v) if len(v) != 1 else v[0] for k, v in lines.items() } return [(Mul.fromiter(nonmatargs), None)] + [(MatrixElement( a, i, j), (i, j)) for (i, j), a in lines.items()] elif expr.is_Add: res = [recurse_expr(i) for i in expr.args] d = collections.defaultdict(list) for res_addend in res: scalar = 1 for elem, indices in res_addend: if indices is None: scalar = elem continue indices = tuple(sorted(indices, key=default_sort_key)) d[indices].append(scalar * remove_matelement(elem, *indices)) scalar = 1 return [(MatrixElement(Add.fromiter(v), *k), k) for k, v in d.items()] elif isinstance(expr, KroneckerDelta): i1, i2 = expr.args if dimensions is not None: identity = Identity(dimensions[0]) else: identity = S.One return [(MatrixElement(identity, i1, i2), (i1, i2))] elif isinstance(expr, MatrixElement): matrix_symbol, i1, i2 = expr.args if i1 in index_ranges: r1, r2 = index_ranges[i1] if r1 != 0 or matrix_symbol.shape[0] != r2 + 1: raise ValueError( "index range mismatch: {} vs. (0, {})".format( (r1, r2), matrix_symbol.shape[0])) if i2 in index_ranges: r1, r2 = index_ranges[i2] if r1 != 0 or matrix_symbol.shape[1] != r2 + 1: raise ValueError( "index range mismatch: {} vs. (0, {})".format( (r1, r2), matrix_symbol.shape[1])) if (i1 == i2) and (i1 in index_ranges): return [(trace(matrix_symbol), None)] return [(MatrixElement(matrix_symbol, i1, i2), (i1, i2))] elif isinstance(expr, Sum): return recurse_expr( expr.args[0], index_ranges={i[0]: i[1:] for i in expr.args[1:]}) else: return [(expr, None)]
def test_Mul(): assert str(x / y) == "x/y" assert str(y / x) == "y/x" assert str(x / y / z) == "x/(y*z)" assert str((x + 1) / (y + 2)) == "(x + 1)/(y + 2)" assert str(2 * x / 3) == '2*x/3' assert str(-2 * x / 3) == '-2*x/3' assert str(-1.0 * x) == '-1.0*x' assert str(1.0 * x) == '1.0*x' assert str(Mul(0, 1, evaluate=False)) == '0*1' assert str(Mul(1, 0, evaluate=False)) == '1*0' assert str(Mul(1, 1, evaluate=False)) == '1*1' assert str(Mul(1, 1, 1, evaluate=False)) == '1*1*1' assert str(Mul(1, 2, evaluate=False)) == '1*2' assert str(Mul(1, S.Half, evaluate=False)) == '1*(1/2)' assert str(Mul(1, 1, S.Half, evaluate=False)) == '1*1*(1/2)' assert str(Mul(1, 1, 2, 3, x, evaluate=False)) == '1*1*2*3*x' assert str(Mul(1, -1, evaluate=False)) == '1*(-1)' assert str(Mul(-1, 1, evaluate=False)) == '(-1)*1' assert str(Mul(4, 3, 2, 1, 0, y, x, evaluate=False)) == '4*3*2*1*0*y*x' assert str(Mul(4, 3, 2, 1 + z, 0, y, x, evaluate=False)) == '4*3*2*(z + 1)*0*y*x' assert str(Mul(Rational(2, 3), Rational(5, 7), evaluate=False)) == '(2/3)*(5/7)' # For issue 14160 assert str( Mul(-2, x, Pow(Mul(y, y, evaluate=False), -1, evaluate=False), evaluate=False)) == '-2*x/(y*y)' class CustomClass1(Expr): is_commutative = True class CustomClass2(Expr): is_commutative = True cc1 = CustomClass1() cc2 = CustomClass2() assert str(Rational(2) * cc1) == '2*CustomClass1()' assert str(cc1 * Rational(2)) == '2*CustomClass1()' assert str(cc1 * Float("1.5")) == '1.5*CustomClass1()' assert str(cc2 * Rational(2)) == '2*CustomClass2()' assert str(cc2 * Rational(2) * cc1) == '2*CustomClass1()*CustomClass2()' assert str(cc1 * Rational(2) * cc2) == '2*CustomClass1()*CustomClass2()'
_height, _monom, _coeff = height, monom, coeff if _height != -1: monom, coeff = _monom, _coeff else: break exponents = [] for m1, m2 in zip(monom, monom[1:] + (0,)): exponents.append(m1 - m2) term = [ s**n for (s, _), n in zip(polys, exponents) ] poly = [ p**n for (_, p), n in zip(polys, exponents) ] symmetric.append(Mul(coeff, *term)) product = poly[0].mul(coeff) for p in poly[1:]: product = product.mul(p) f -= product result.append((Add(*symmetric), f.as_expr())) polys = [ (s, p.as_expr()) for s, p in polys ] if not opt.formal: for i, (sym, non_sym) in enumerate(result): result[i] = (sym.subs(polys), non_sym)
def mrv(e, x): """Returns a SubsSet of most rapidly varying (mrv) subexpressions of 'e', and e rewritten in terms of these""" e = powsimp(e, deep=True, combine="exp") if not isinstance(e, Basic): raise TypeError("e should be an instance of Basic") if not e.has(x): return SubsSet(), e elif e == x: s = SubsSet() return s, s[x] elif e.is_Mul or e.is_Add: i, d = e.as_independent(x) # throw away x-independent terms if d.func != e.func: s, expr = mrv(d, x) return s, e.func(i, expr) a, b = d.as_two_terms() s1, e1 = mrv(a, x) s2, e2 = mrv(b, x) return mrv_max1(s1, s2, e.func(i, e1, e2), x) elif e.is_Pow: b, e = e.as_base_exp() if e.has(x): return mrv(exp(e * log(b)), x) else: s, expr = mrv(b, x) return s, expr ** e elif e.func is log: s, expr = mrv(e.args[0], x) return s, log(expr) elif e.func is exp: # We know from the theory of this algorithm that exp(log(...)) may always # be simplified here, and doing so is vital for termination. if e.args[0].func is log: return mrv(e.args[0].args[0], x) # if a product has an infinite factor the result will be # infinite if there is no zero, otherwise NaN; here, we # consider the result infinite if any factor is infinite li = limitinf(e.args[0], x) if any(_.is_infinite for _ in Mul.make_args(li)): s1 = SubsSet() e1 = s1[e] s2, e2 = mrv(e.args[0], x) su = s1.union(s2)[0] su.rewrites[e1] = exp(e2) return mrv_max3(s1, e1, s2, exp(e2), su, e1, x) else: s, expr = mrv(e.args[0], x) return s, exp(expr) elif e.is_Function: l = [mrv(a, x) for a in e.args] l2 = [s for (s, _) in l if s != SubsSet()] if len(l2) != 1: # e.g. something like BesselJ(x, x) raise NotImplementedError("MRV set computation for functions in" " several variables not implemented.") s, ss = l2[0], SubsSet() args = [ss.do_subs(x[1]) for x in l] return s, e.func(*args) elif e.is_Derivative: raise NotImplementedError("MRV set computation for derviatives" " not implemented yet.") return mrv(e.args[0], x) raise NotImplementedError("Don't know how to calculate the mrv of '%s'" % e)
def __refactor(val): args = Mul.make_args(val) sumv = next(x for x in args if isinstance(x, Sum)) constant = Mul(*[x for x in args if x != sumv]) return Sum(constant * sumv.function, *sumv.limits)
def is_log_deriv_k_t_radical(fa, fd, DE, Df=True): """ Checks if Df is the logarithmic derivative of a k(t)-radical. b in k(t) can be written as the logarithmic derivative of a k(t) radical if there exist n in ZZ and u in k(t) with n, u != 0 such that n*b == Du/u. Either returns (ans, u, n, const) or None, which means that Df cannot be written as the logarithmic derivative of a k(t)-radical. ans is a list of tuples such that Mul(*[i**j for i, j in ans]) == u. This is useful for seeing exactly what elements of k(t) produce u. This function uses the structure theorem approach, which says that for any f in K, Df is the logarithmic derivative of a K-radical if and only if there are ri in QQ such that:: --- --- Dt \ r * Dt + \ r * i / i i / i --- = Df. --- --- t i in L i in E i K/C(x) K/C(x) Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of hyperexponential monomials of K over C(x)). If K is an elementary extension over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the transcendence degree of K over C(x). Furthermore, because Const_D(K) == Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) and L_K/C(x) are disjoint. The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed recursively using this same function. Therefore, it is required to pass them as indices to D (or T). L_args are the arguments of the logarithms indexed by L_K (i.e., if i is in L_K, then T[i] == log(L_args[i])). This is needed to compute the final answer u such that n*f == Du/u. exp(f) will be the same as u up to a multiplicative constant. This is because they will both behave the same as monomials. For example, both exp(x) and exp(x + 1) == E*exp(x) satisfy Dt == t. Therefore, the term const is returned. const is such that exp(const)*f == u. This is calculated by subtracting the arguments of one exponential from the other. Therefore, it is necessary to pass the arguments of the exponential terms in E_args. To handle the case where we are given Df, not f, use is_log_deriv_k_t_radical_in_field(). """ H = [] if Df: dfa, dfd = (fd*derivation(fa, DE) - fa*derivation(fd, DE)).cancel(fd**2, include=True) else: dfa, dfd = fa, fd # Our assumption here is that each monomial is recursively transcendental if len(DE.L_K) + len(DE.E_K) != len(DE.D) - 1: if [i for i in DE.cases if i == 'tan'] or \ set([i for i in DE.cases if i == 'primitive']) - set(DE.L_K): raise NotImplementedError("Real version of the structure " "theorems with hypertangent support is not yet implemented.") # TODO: What should really be done in this case? raise NotImplementedError("Nonelementary extensions not supported " "in the structure theorems.") E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.E_K] L_part = [DE.D[i].as_expr() for i in DE.L_K] lhs = Matrix([E_part + L_part]) rhs = Matrix([dfa.as_expr()/dfd.as_expr()]) A, u = constant_system(lhs, rhs, DE) if not all(derivation(i, DE, basic=True).is_zero for i in u) or not A: # If the elements of u are not all constant # Note: See comment in constant_system # Also note: derivation(basic=True) calls cancel() return None else: if not all(i.is_Rational for i in u): # TODO: But maybe we can tell if they're not rational, like # log(2)/log(3). Also, there should be an option to continue # anyway, even if the result might potentially be wrong. raise NotImplementedError("Cannot work with non-rational " "coefficients in this case.") else: n = reduce(ilcm, [i.as_numer_denom()[1] for i in u]) u *= n terms = [DE.T[i] for i in DE.E_K] + DE.L_args ans = list(zip(terms, u)) result = Mul(*[Pow(i, j) for i, j in ans]) # exp(f) will be the same as result up to a multiplicative # constant. We now find the log of that constant. argterms = DE.E_args + [DE.T[i] for i in DE.L_K] const = cancel(fa.as_expr()/fd.as_expr() - Add(*[Mul(i, j/n) for i, j in zip(argterms, u)])) return (ans, result, n, const)
def is_deriv_k(fa, fd, DE): """ Checks if Df/f is the derivative of an element of k(t). a in k(t) is the derivative of an element of k(t) if there exists b in k(t) such that a = Db. Either returns (ans, u), such that Df/f == Du, or None, which means that Df/f is not the derivative of an element of k(t). ans is a list of tuples such that Add(*[i*j for i, j in ans]) == u. This is useful for seeing exactly which elements of k(t) produce u. This function uses the structure theorem approach, which says that for any f in K, Df/f is the derivative of a element of K if and only if there are ri in QQ such that:: --- --- Dt \ r * Dt + \ r * i Df / i i / i --- = --. --- --- t f i in L i in E i K/C(x) K/C(x) Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of hyperexponential monomials of K over C(x)). If K is an elementary extension over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the transcendence degree of K over C(x). Furthermore, because Const_D(K) == Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) and L_K/C(x) are disjoint. The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed recursively using this same function. Therefore, it is required to pass them as indices to D (or T). E_args are the arguments of the hyperexponentials indexed by E_K (i.e., if i is in E_K, then T[i] == exp(E_args[i])). This is needed to compute the final answer u such that Df/f == Du. log(f) will be the same as u up to a additive constant. This is because they will both behave the same as monomials. For example, both log(x) and log(2*x) == log(x) + log(2) satisfy Dt == 1/x, because log(2) is constant. Therefore, the term const is returned. const is such that log(const) + f == u. This is calculated by dividing the arguments of one logarithm from the other. Therefore, it is necessary to pass the arguments of the logarithmic terms in L_args. To handle the case where we are given Df/f, not f, use is_deriv_k_in_field(). """ # Compute Df/f dfa, dfd = fd*(fd*derivation(fa, DE) - fa*derivation(fd, DE)), fd**2*fa dfa, dfd = dfa.cancel(dfd, include=True) # Our assumption here is that each monomial is recursively transcendental if len(DE.L_K) + len(DE.E_K) != len(DE.D) - 1: if [i for i in DE.cases if i == 'tan'] or \ set([i for i in DE.cases if i == 'primitive']) - set(DE.L_K): raise NotImplementedError("Real version of the structure " "theorems with hypertangent support is not yet implemented.") # TODO: What should really be done in this case? raise NotImplementedError("Nonelementary extensions not supported " "in the structure theorems.") E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.E_K] L_part = [DE.D[i].as_expr() for i in DE.L_K] lhs = Matrix([E_part + L_part]) rhs = Matrix([dfa.as_expr()/dfd.as_expr()]) A, u = constant_system(lhs, rhs, DE) if not all(derivation(i, DE, basic=True).is_zero for i in u) or not A: # If the elements of u are not all constant # Note: See comment in constant_system # Also note: derivation(basic=True) calls cancel() return None else: if not all(i.is_Rational for i in u): raise NotImplementedError("Cannot work with non-rational " "coefficients in this case.") else: terms = DE.E_args + [DE.T[i] for i in DE.L_K] ans = list(zip(terms, u)) result = Add(*[Mul(i, j) for i, j in ans]) argterms = [DE.T[i] for i in DE.E_K] + DE.L_args l = [] ld = [] for i, j in zip(argterms, u): # We need to get around things like sqrt(x**2) != x # and also sqrt(x**2 + 2*x + 1) != x + 1 # Issue 10798: i need not be a polynomial i, d = i.as_numer_denom() icoeff, iterms = sqf_list(i) l.append(Mul(*([Pow(icoeff, j)] + [Pow(b, e*j) for b, e in iterms]))) dcoeff, dterms = sqf_list(d) ld.append(Mul(*([Pow(dcoeff, j)] + [Pow(b, e*j) for b, e in dterms]))) const = cancel(fa.as_expr()/fd.as_expr()/Mul(*l)*Mul(*ld)) return (ans, result, const)
A = is_log_deriv_k_t_radical_in_field(pa, pd, DE, case='auto') if A is None: return None n, u = A elif case == 'base': # TODO: we can use more efficient residue reduction from ratint() if not fd.is_sqf or fa.degree() >= fd.degree(): # f is the logarithmic derivative in the base case if and only if # f = fa/fd, fd is square-free, deg(fa) < deg(fd), and # gcd(fa, fd) == 1. The last condition is handled by cancel() above. return None # Note: if residueterms = [], returns (1, 1) # f had better be 0 in that case. n = reduce(ilcm, [i.as_numer_denom()[1] for _, i in residueterms], S(1)) u = Mul(*[Pow(i, j*n) for i, j in residueterms]) return (n, u) elif case == 'tan': raise NotImplementedError("The hypertangent case is " "not yet implemented for is_log_deriv_k_t_radical_in_field()") elif case in ['other_linear', 'other_nonlinear']: # XXX: If these are supported by the structure theorems, change to NotImplementedError. raise ValueError("The %s case is not supported in this function." % case) else: raise ValueError("case must be one of {'primitive', 'exp', 'tan', " "'base', 'auto'}, not %s" % case) common_denom = reduce(ilcm, [i.as_numer_denom()[1] for i in [j for _, j in
def test_add(): with evaluate(False): expr = x + x assert isinstance(expr, Add) assert expr.args == (x, x) with evaluate(True): assert (x + x).args == (2, x) assert (x + x).args == (x, x) assert isinstance(x + x, Mul) with evaluate(False): assert S(1) + 1 == Add(1, 1) assert 1 + S(1) == Add(1, 1) assert S(4) - 3 == Add(4, -3) assert -3 + S(4) == Add(4, -3) assert S(2) * 4 == Mul(2, 4) assert 4 * S(2) == Mul(2, 4) assert S(6) / 3 == Mul(6, S(1) / 3) assert S(1) / 3 * 6 == Mul(S(1) / 3, 6) assert 9**S(2) == Pow(9, 2) assert S(2)**9 == Pow(2, 9) assert S(2) / 2 == Mul(2, S(1) / 2) assert S(1) / 2 * 2 == Mul(S(1) / 2, 2) assert S(2) / 3 + 1 == Add(S(2) / 3, 1) assert 1 + S(2) / 3 == Add(1, S(2) / 3) assert S(4) / 7 - 3 == Add(S(4) / 7, -3) assert -3 + S(4) / 7 == Add(-3, S(4) / 7) assert S(2) / 4 * 4 == Mul(S(2) / 4, 4) assert 4 * (S(2) / 4) == Mul(4, S(2) / 4) assert S(6) / 3 == Mul(6, S(1) / 3) assert S(1) / 3 * 6 == Mul(S(1) / 3, 6) assert S(1) / 3 + sqrt(3) == Add(S(1) / 3, sqrt(3)) assert sqrt(3) + S(1) / 3 == Add(sqrt(3), S(1) / 3) assert S(1) / 2 * 10.333 == Mul(S(1) / 2, 10.333) assert 10.333 * S(1) / 2 == Mul(10.333, S(1) / 2) assert sqrt(2) * sqrt(2) == Mul(sqrt(2), sqrt(2)) assert S(1) / 2 + x == Add(S(1) / 2, x) assert x + S(1) / 2 == Add(x, S(1) / 2) assert S(1) / x * x == Mul(S(1) / x, x) assert x * S(1) / x == Mul(x, S(1) / x)
def symmetrize(F, *gens, **args): """ Rewrite a polynomial in terms of elementary symmetric polynomials. A symmetric polynomial is a multivariate polynomial that remains invariant under any variable permutation, i.e., if ``f = f(x_1, x_2, ..., x_n)``, then ``f = f(x_{i_1}, x_{i_2}, ..., x_{i_n})``, where ``(i_1, i_2, ..., i_n)`` is a permutation of ``(1, 2, ..., n)`` (an element of the group ``S_n``). Returns a tuple of symmetric polynomials ``(f1, f2, ..., fn)`` such that ``f = f1 + f2 + ... + fn``. Examples ======== >>> from sympy.polys.polyfuncs import symmetrize >>> from sympy.abc import x, y >>> symmetrize(x**2 + y**2) (-2*x*y + (x + y)**2, 0) >>> symmetrize(x**2 + y**2, formal=True) (s1**2 - 2*s2, 0, [(s1, x + y), (s2, x*y)]) >>> symmetrize(x**2 - y**2) (-2*x*y + (x + y)**2, -2*y**2) >>> symmetrize(x**2 - y**2, formal=True) (s1**2 - 2*s2, -2*y**2, [(s1, x + y), (s2, x*y)]) """ allowed_flags(args, ['formal', 'symbols']) iterable = True if not hasattr(F, '__iter__'): iterable = False F = [F] try: F, opt = parallel_poly_from_expr(F, *gens, **args) except PolificationFailed as exc: result = [] for expr in exc.exprs: if expr.is_Number: result.append((expr, S.Zero)) else: raise ComputationFailed('symmetrize', len(F), exc) else: if not iterable: result, = result if not exc.opt.formal: return result else: if iterable: return result, [] else: return result + ([], ) polys, symbols = [], opt.symbols gens, dom = opt.gens, opt.domain for i in range(len(gens)): poly = symmetric_poly(i + 1, gens, polys=True) polys.append((next(symbols), poly.set_domain(dom))) indices = list(range(len(gens) - 1)) weights = list(range(len(gens), 0, -1)) result = [] for f in F: symmetric = [] if not f.is_homogeneous: symmetric.append(f.TC()) f -= f.TC() while f: _height, _monom, _coeff = -1, None, None for i, (monom, coeff) in enumerate(f.terms()): if all(monom[i] >= monom[i + 1] for i in indices): height = max([n * m for n, m in zip(weights, monom)]) if height > _height: _height, _monom, _coeff = height, monom, coeff if _height != -1: monom, coeff = _monom, _coeff else: break exponents = [] for m1, m2 in zip(monom, monom[1:] + (0, )): exponents.append(m1 - m2) term = [s**n for (s, _), n in zip(polys, exponents)] poly = [p**n for (_, p), n in zip(polys, exponents)] symmetric.append(Mul(coeff, *term)) product = poly[0].mul(coeff) for p in poly[1:]: product = product.mul(p) f -= product result.append((Add(*symmetric), f.as_expr())) polys = [(s, p.as_expr()) for s, p in polys] if not opt.formal: for i, (sym, non_sym) in enumerate(result): result[i] = (sym.subs(polys), non_sym) if not iterable: result, = result if not opt.formal: return result else: if iterable: return result, polys else: return result + (polys, )
def f(rv): if not (rv.is_Add or rv.is_Mul): return rv def gooda(a): # bool to tell whether the leading ``a`` in ``a*log(x)`` # could appear as log(x**a) return (a is not S.NegativeOne and # -1 *could* go, but we disallow (a.is_real or force and a.is_real is not False)) def goodlog(l): # bool to tell whether log ``l``'s argument can combine with others a = l.args[0] return a.is_positive or force and a.is_nonpositive is not False other = [] logs = [] log1 = defaultdict(list) for a in Add.make_args(rv): if a.func is log and goodlog(a): log1[()].append(([], a)) elif not a.is_Mul: other.append(a) else: ot = [] co = [] lo = [] for ai in a.args: if ai.is_Rational and ai < 0: ot.append(S.NegativeOne) co.append(-ai) elif ai.func is log and goodlog(ai): lo.append(ai) elif gooda(ai): co.append(ai) else: ot.append(ai) if len(lo) > 1: logs.append((ot, co, lo)) elif lo: log1[tuple(ot)].append((co, lo[0])) else: other.append(a) # if there is only one log at each coefficient and none have # an exponent to place inside the log then there is nothing to do if not logs and all(len(log1[k]) == 1 and log1[k][0] == [] for k in log1): return rv # collapse multi-logs as far as possible in a canonical way # TODO: see if x*log(a)+x*log(a)*log(b) -> x*log(a)*(1+log(b))? # -- in this case, it's unambiguous, but if it were were a log(c) in # each term then it's arbitrary whether they are grouped by log(a) or # by log(c). So for now, just leave this alone; it's probably better to # let the user decide for o, e, l in logs: l = list(ordered(l)) e = log(l.pop(0).args[0]**Mul(*e)) while l: li = l.pop(0) e = log(li.args[0]**e) c, l = Mul(*o), e if l.func is log: # it should be, but check to be sure log1[(c,)].append(([], l)) else: other.append(c*l) # logs that have the same coefficient can multiply for k in list(log1.keys()): log1[Mul(*k)] = log(logcombine(Mul(*[ l.args[0]**Mul(*c) for c, l in log1.pop(k)]), force=force)) # logs that have oppositely signed coefficients can divide for k in ordered(list(log1.keys())): if not k in log1: # already popped as -k continue if -k in log1: # figure out which has the minus sign; the one with # more op counts should be the one num, den = k, -k if num.count_ops() > den.count_ops(): num, den = den, num other.append(num*log(log1.pop(num).args[0]/log1.pop(den).args[0])) else: other.append(k*log1.pop(k)) return Add(*other)
def mrv(e, x): """Returns a SubsSet of most rapidly varying (mrv) subexpressions of 'e', and e rewritten in terms of these""" e = powsimp(e, deep=True, combine='exp') if not isinstance(e, Basic): raise TypeError("e should be an instance of Basic") if not e.has(x): return SubsSet(), e elif e == x: s = SubsSet() return s, s[x] elif e.is_Mul or e.is_Add: i, d = e.as_independent(x) # throw away x-independent terms if d.func != e.func: s, expr = mrv(d, x) return s, e.func(i, expr) a, b = d.as_two_terms() s1, e1 = mrv(a, x) s2, e2 = mrv(b, x) return mrv_max1(s1, s2, e.func(i, e1, e2), x) elif e.is_Pow: e1 = S.One while e.is_Pow: b1 = e.base e1 *= e.exp e = b1 if b1 == 1: return SubsSet(), b1 if e1.has(x): base_lim = limitinf(b1, x) if base_lim is S.One: return mrv(exp(e1 * (b1 - 1)), x) return mrv(exp(e1 * log(b1)), x) else: s, expr = mrv(b1, x) return s, expr**e1 elif isinstance(e, log): s, expr = mrv(e.args[0], x) return s, log(expr) elif isinstance(e, exp): # We know from the theory of this algorithm that exp(log(...)) may always # be simplified here, and doing so is vital for termination. if isinstance(e.args[0], log): return mrv(e.args[0].args[0], x) # if a product has an infinite factor the result will be # infinite if there is no zero, otherwise NaN; here, we # consider the result infinite if any factor is infinite li = limitinf(e.args[0], x) if any(_.is_infinite for _ in Mul.make_args(li)): s1 = SubsSet() e1 = s1[e] s2, e2 = mrv(e.args[0], x) su = s1.union(s2)[0] su.rewrites[e1] = exp(e2) return mrv_max3(s1, e1, s2, exp(e2), su, e1, x) else: s, expr = mrv(e.args[0], x) return s, exp(expr) elif e.is_Function: l = [mrv(a, x) for a in e.args] l2 = [s for (s, _) in l if s != SubsSet()] if len(l2) != 1: # e.g. something like BesselJ(x, x) raise NotImplementedError("MRV set computation for functions in" " several variables not implemented.") s, ss = l2[0], SubsSet() args = [ss.do_subs(x[1]) for x in l] return s, e.func(*args) elif e.is_Derivative: raise NotImplementedError("MRV set computation for derviatives" " not implemented yet.") return mrv(e.args[0], x) raise NotImplementedError("Don't know how to calculate the mrv of '%s'" % e)
def handle(expr): # Handle first reduces to the case # expr = 1/d, where d is an add, or d is base**p/2. # We do this by recursively calling handle on each piece. from sympy.simplify.simplify import nsimplify n, d = fraction(expr) if expr.is_Atom or (d.is_Atom and n.is_Atom): return expr elif not n.is_Atom: n = n.func(*[handle(a) for a in n.args]) return _unevaluated_Mul(n, handle(1/d)) elif n is not S.One: return _unevaluated_Mul(n, handle(1/d)) elif d.is_Mul: return _unevaluated_Mul(*[handle(1/d) for d in d.args]) # By this step, expr is 1/d, and d is not a mul. if not symbolic and d.free_symbols: return expr if ispow2(d): d2 = sqrtdenest(sqrt(d.base))**numer(d.exp) if d2 != d: return handle(1/d2) elif d.is_Pow and (d.exp.is_integer or d.base.is_positive): # (1/d**i) = (1/d)**i return handle(1/d.base)**d.exp if not (d.is_Add or ispow2(d)): return 1/d.func(*[handle(a) for a in d.args]) # handle 1/d treating d as an Add (though it may not be) keep = True # keep changes that are made # flatten it and collect radicals after checking for special # conditions d = _mexpand(d) # did it change? if d.is_Atom: return 1/d # is it a number that might be handled easily? if d.is_number: _d = nsimplify(d) if _d.is_Number and _d.equals(d): return 1/_d while True: # collect similar terms collected = defaultdict(list) for m in Add.make_args(d): # d might have become non-Add p2 = [] other = [] for i in Mul.make_args(m): if ispow2(i, log2=True): p2.append(i.base if i.exp is S.Half else i.base**(2*i.exp)) elif i is S.ImaginaryUnit: p2.append(S.NegativeOne) else: other.append(i) collected[tuple(ordered(p2))].append(Mul(*other)) rterms = list(ordered(list(collected.items()))) rterms = [(Mul(*i), Add(*j)) for i, j in rterms] nrad = len(rterms) - (1 if rterms[0][0] is S.One else 0) if nrad < 1: break elif nrad > max_terms: # there may have been invalid operations leading to this point # so don't keep changes, e.g. this expression is troublesome # in collecting terms so as not to raise the issue of 2834: # r = sqrt(sqrt(5) + 5) # eq = 1/(sqrt(5)*r + 2*sqrt(5)*sqrt(-sqrt(5) + 5) + 5*r) keep = False break if len(rterms) > 4: # in general, only 4 terms can be removed with repeated squaring # but other considerations can guide selection of radical terms # so that radicals are removed if all([x.is_Integer and (y**2).is_Rational for x, y in rterms]): nd, d = rad_rationalize(S.One, Add._from_args( [sqrt(x)*y for x, y in rterms])) n *= nd else: # is there anything else that might be attempted? keep = False break from sympy.simplify.powsimp import powsimp, powdenest num = powsimp(_num(rterms)) n *= num d *= num d = powdenest(_mexpand(d), force=symbolic) if d.is_Atom: break if not keep: return expr return _unevaluated_Mul(n, 1/d)
def __trigsimp(expr, deep=False): """recursive helper for trigsimp""" from sympy.simplify.fu import TR10i if _trigpat is None: _trigpats() a, b, c, d, matchers_division, matchers_add, \ matchers_identity, artifacts = _trigpat if expr.is_Mul: # do some simplifications like sin/cos -> tan: if not expr.is_commutative: com, nc = expr.args_cnc() expr = _trigsimp(Mul._from_args(com), deep)*Mul._from_args(nc) else: for i, (pattern, simp, ok1, ok2) in enumerate(matchers_division): if not _dotrig(expr, pattern): continue newexpr = _match_div_rewrite(expr, i) if newexpr is not None: if newexpr != expr: expr = newexpr break else: continue # use SymPy matching instead res = expr.match(pattern) if res and res.get(c, 0): if not res[c].is_integer: ok = ok1.subs(res) if not ok.is_positive: continue ok = ok2.subs(res) if not ok.is_positive: continue # if "a" contains any of trig or hyperbolic funcs with # argument "b" then skip the simplification if any(w.args[0] == res[b] for w in res[a].atoms( TrigonometricFunction, HyperbolicFunction)): continue # simplify and finish: expr = simp.subs(res) break # process below if expr.is_Add: args = [] for term in expr.args: if not term.is_commutative: com, nc = term.args_cnc() nc = Mul._from_args(nc) term = Mul._from_args(com) else: nc = S.One term = _trigsimp(term, deep) for pattern, result in matchers_identity: res = term.match(pattern) if res is not None: term = result.subs(res) break args.append(term*nc) if args != expr.args: expr = Add(*args) expr = min(expr, expand(expr), key=count_ops) if expr.is_Add: for pattern, result in matchers_add: if not _dotrig(expr, pattern): continue expr = TR10i(expr) if expr.has(HyperbolicFunction): res = expr.match(pattern) # if "d" contains any trig or hyperbolic funcs with # argument "a" or "b" then skip the simplification; # this isn't perfect -- see tests if res is None or not (a in res and b in res) or any( w.args[0] in (res[a], res[b]) for w in res[d].atoms( TrigonometricFunction, HyperbolicFunction)): continue expr = result.subs(res) break # Reduce any lingering artifacts, such as sin(x)**2 changing # to 1 - cos(x)**2 when sin(x)**2 was "simpler" for pattern, result, ex in artifacts: if not _dotrig(expr, pattern): continue # Substitute a new wild that excludes some function(s) # to help influence a better match. This is because # sometimes, for example, 'a' would match sec(x)**2 a_t = Wild('a', exclude=[ex]) pattern = pattern.subs(a, a_t) result = result.subs(a, a_t) m = expr.match(pattern) was = None while m and was != expr: was = expr if m[a_t] == 0 or \ -m[a_t] in m[c].args or m[a_t] + m[c] == 0: break if d in m and m[a_t]*m[d] + m[c] == 0: break expr = result.subs(m) m = expr.match(pattern) m.setdefault(c, S.Zero) elif expr.is_Mul or expr.is_Pow or deep and expr.args: expr = expr.func(*[_trigsimp(a, deep) for a in expr.args]) try: if not expr.has(*_trigs): raise TypeError e = expr.atoms(exp) new = expr.rewrite(exp, deep=deep) if new == e: raise TypeError fnew = factor(new) if fnew != new: new = sorted([new, factor(new)], key=count_ops)[0] # if all exp that were introduced disappeared then accept it if not (new.atoms(exp) - e): expr = new except TypeError: pass return expr
def doit(self, **hints): """Evaluates limit""" from sympy.series.limitseq import limit_seq from sympy.functions import RisingFactorial e, z, z0, dir = self.args if hints.get('deep', True): e = e.doit(**hints) z = z.doit(**hints) z0 = z0.doit(**hints) if e == z: return z0 if not e.has(z): return e # gruntz fails on factorials but works with the gamma function # If no factorial term is present, e should remain unchanged. # factorial is defined to be zero for negative inputs (which # differs from gamma) so only rewrite for positive z0. if z0.is_positive: e = e.rewrite([factorial, RisingFactorial], gamma) if e.is_Mul: if abs(z0) is S.Infinity: e = factor_terms(e) e = e.rewrite(fibonacci, GoldenRatio) ok = lambda w: (z in w.free_symbols and any( a.is_polynomial(z) or any( z in m.free_symbols and m.is_polynomial(z) for m in Mul.make_args(a)) for a in Add.make_args(w))) if all(ok(w) for w in e.as_numer_denom()): u = Dummy(positive=True) if z0 is S.NegativeInfinity: inve = e.subs(z, -1 / u) else: inve = e.subs(z, 1 / u) r = limit(inve.as_leading_term(u), u, S.Zero, "+") if isinstance(r, Limit): return self else: return r if e.is_Order: return Order(limit(e.expr, z, z0), *e.args[1:]) try: r = gruntz(e, z, z0, dir) if r is S.NaN: raise PoleError() except (PoleError, ValueError): r = heuristics(e, z, z0, dir) if r is None: return self except NotImplementedError: # Trying finding limits of sequences if hints.get('sequence', True) and z0 is S.Infinity: trials = hints.get('trials', 5) r = limit_seq(e, z, trials) if r is None: raise NotImplementedError() else: raise NotImplementedError() return r
def parse_expression(terms, pattern): """Parse terms searching for a pattern. Terms is a list of tuples as returned by parse_terms; Pattern is an expression treated as a product of factors. """ pattern = Mul.make_args(pattern) if len(terms) < len(pattern): # pattern is longer than matched product # so no chance for positive parsing result return None else: pattern = [parse_term(elem) for elem in pattern] terms = terms[:] # need a copy elems, common_expo, has_deriv = [], None, False for elem, e_rat, e_sym, e_ord in pattern: if elem.is_Number and e_rat == 1 and e_sym is None: # a constant is a match for everything continue for j in range(len(terms)): if terms[j] is None: continue term, t_rat, t_sym, t_ord = terms[j] # keeping track of whether one of the terms had # a derivative or not as this will require rebuilding # the expression later if t_ord is not None: has_deriv = True if (term.match(elem) is not None and (t_sym == e_sym or t_sym is not None and e_sym is not None and t_sym.match(e_sym) is not None)): if exact is False: # we don't have to be exact so find common exponent # for both expression's term and pattern's element expo = t_rat / e_rat if common_expo is None: # first time common_expo = expo else: # common exponent was negotiated before so # there is no chance for a pattern match unless # common and current exponents are equal if common_expo != expo: common_expo = 1 else: # we ought to be exact so all fields of # interest must match in every details if e_rat != t_rat or e_ord != t_ord: continue # found common term so remove it from the expression # and try to match next element in the pattern elems.append(terms[j]) terms[j] = None break else: # pattern element not found return None return [_f for _f in terms if _f], elems, common_expo, has_deriv
def _eval_determinant(self): from sympy.matrices.expressions.determinant import Determinant factor, matrices = self.as_coeff_matrices() square_matrices = only_squares(*matrices) return factor**self.rows * Mul(*map(Determinant, square_matrices))
def repl(nu, z): if factors.intersection(Mul.make_args(z)): return to(nu, z) return fro(nu, z)
def _print_Mul(self, expr): # print complex numbers nicely in Julia if (expr.is_number and expr.is_imaginary and expr.as_coeff_Mul()[0].is_integer): return "%sim" % self._print(-S.ImaginaryUnit * expr) # cribbed from str.py prec = precedence(expr) c, e = expr.as_coeff_Mul() if c < 0: expr = _keep_coeff(-c, e) sign = "-" else: sign = "" a = [] # items in the numerator b = [] # items that are in the denominator (if any) pow_paren = [ ] # Will collect all pow with more than one base element and exp = -1 if self.order not in ('old', 'none'): args = expr.as_ordered_factors() else: # use make_args in case expr was something like -x -> x args = Mul.make_args(expr) # Gather args for numerator/denominator for item in args: if (item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative): if item.exp != -1: b.append(Pow(item.base, -item.exp, evaluate=False)) else: if len(item.args[0].args) != 1 and isinstance( item.base, Mul): # To avoid situations like #14160 pow_paren.append(item) b.append(Pow(item.base, -item.exp)) elif item.is_Rational and item is not S.Infinity: if item.p != 1: a.append(Rational(item.p)) if item.q != 1: b.append(Rational(item.q)) else: a.append(item) a = a or [S.One] a_str = [self.parenthesize(x, prec) for x in a] b_str = [self.parenthesize(x, prec) for x in b] # To parenthesize Pow with exp = -1 and having more than one Symbol for item in pow_paren: if item.base in b: b_str[b.index(item.base)] = "(%s)" % b_str[b.index(item.base)] # from here it differs from str.py to deal with "*" and ".*" def multjoin(a, a_str): # here we probably are assuming the constants will come first r = a_str[0] for i in range(1, len(a)): mulsym = '*' if a[i - 1].is_number else '.*' r = r + mulsym + a_str[i] return r if not b: return sign + multjoin(a, a_str) elif len(b) == 1: divsym = '/' if b[0].is_number else './' return sign + multjoin(a, a_str) + divsym + b_str[0] else: divsym = '/' if all(bi.is_number for bi in b) else './' return (sign + multjoin(a, a_str) + divsym + "(%s)" % multjoin(b, b_str))
def nc_gcd(aa, bb): a, b = [i.as_coeff_Mul() for i in [aa, bb]] c = gcd(a[0], b[0]).as_numer_denom()[0] g = Mul(*(a[1].args_cnc(cset=True)[0] & b[1].args_cnc(cset=True)[0])) return _keep_coeff(c, g)
def __new__(cls, expr, *args, **kwargs): expr = sympify(expr) if not args: if expr.is_Order: variables = expr.variables point = expr.point else: variables = list(expr.free_symbols) point = [S.Zero]*len(variables) else: args = list(args if is_sequence(args) else [args]) variables, point = [], [] if is_sequence(args[0]): for a in args: v, p = list(map(sympify, a)) variables.append(v) point.append(p) else: variables = list(map(sympify, args)) point = [S.Zero]*len(variables) if not all(isinstance(v, Symbol) for v in variables): raise TypeError('Variables are not symbols, got %s' % variables) if len(list(uniq(variables))) != len(variables): raise ValueError('Variables are supposed to be unique symbols, got %s' % variables) if expr.is_Order: expr_vp = dict(expr.args[1:]) new_vp = dict(expr_vp) vp = dict(zip(variables, point)) for v, p in vp.items(): if v in new_vp.keys(): if p != new_vp[v]: raise NotImplementedError( "Mixing Order at different points is not supported.") else: new_vp[v] = p if set(expr_vp.keys()) == set(new_vp.keys()): return expr else: variables = list(new_vp.keys()) point = [new_vp[v] for v in variables] if expr is S.NaN: return S.NaN if not all(p is S.Zero for p in point) and \ not all(p is S.Infinity for p in point): raise NotImplementedError('Order at points other than 0 ' 'or oo not supported, got %s as a point.' % point) if variables: if len(variables) > 1: # XXX: better way? We need this expand() to # workaround e.g: expr = x*(x + y). # (x*(x + y)).as_leading_term(x, y) currently returns # x*y (wrong order term!). That's why we want to deal with # expand()'ed expr (handled in "if expr.is_Add" branch below). expr = expr.expand() if expr.is_Add: lst = expr.extract_leading_order(variables, point) expr = Add(*[f.expr for (e, f) in lst]) elif expr: if point[0] == S.Zero: expr = expr.as_leading_term(*variables) expr = expr.as_independent(*variables, as_Add=False)[1] expr = expand_power_base(expr) expr = expand_log(expr) if len(variables) == 1: # The definition of O(f(x)) symbol explicitly stated that # the argument of f(x) is irrelevant. That's why we can # combine some power exponents (only "on top" of the # expression tree for f(x)), e.g.: # x**p * (-x)**q -> x**(p+q) for real p, q. x = variables[0] margs = list(Mul.make_args( expr.as_independent(x, as_Add=False)[1])) for i, t in enumerate(margs): if t.is_Pow: b, q = t.args if b in (x, -x) and q.is_real and not q.has(x): margs[i] = x**q elif b.is_Pow and not b.exp.has(x): b, r = b.args if b in (x, -x) and r.is_real: margs[i] = x**(r*q) elif b.is_Mul and b.args[0] is S.NegativeOne: b = -b if b.is_Pow and not b.exp.has(x): b, r = b.args if b in (x, -x) and r.is_real: margs[i] = x**(r*q) expr = Mul(*margs) if expr is S.Zero: return expr if expr.is_Order: expr = expr.expr if not expr.has(*variables): expr = S.One # create Order instance: variables.sort(key=default_sort_key) args = (expr,) + Tuple(*zip(variables, point)) obj = Expr.__new__(cls, *args) return obj
def _denest_pow(eq): """ Denest powers. This is a helper function for powdenest that performs the actual transformation. """ from sympy.simplify.simplify import logcombine b, e = eq.as_base_exp() if b.is_Pow or isinstance(b.func, exp) and e != 1: new = b._eval_power(e) if new is not None: eq = new b, e = new.as_base_exp() # denest exp with log terms in exponent if b is S.Exp1 and e.is_Mul: logs = [] other = [] for ei in e.args: if any(ai.func is log for ai in Add.make_args(ei)): logs.append(ei) else: other.append(ei) logs = logcombine(Mul(*logs)) return Pow(exp(logs), Mul(*other)) _, be = b.as_base_exp() if be is S.One and not (b.is_Mul or b.is_Rational and b.q != 1 or b.is_positive): return eq # denest eq which is either pos**e or Pow**e or Mul**e or # Mul(b1**e1, b2**e2) # handle polar numbers specially polars, nonpolars = [], [] for bb in Mul.make_args(b): if bb.is_polar: polars.append(bb.as_base_exp()) else: nonpolars.append(bb) if len(polars) == 1 and not polars[0][0].is_Mul: return Pow(polars[0][0], polars[0][1] * e) * powdenest( Mul(*nonpolars)**e) elif polars: return Mul(*[powdenest(bb**(ee*e)) for (bb, ee) in polars]) \ *powdenest(Mul(*nonpolars)**e) if b.is_Integer: # use log to see if there is a power here logb = expand_log(log(b)) if logb.is_Mul: c, logb = logb.args e *= c base = logb.args[0] return Pow(base, e) # if b is not a Mul or any factor is an atom then there is nothing to do if not b.is_Mul or any(s.is_Atom for s in Mul.make_args(b)): return eq # let log handle the case of the base of the argument being a Mul, e.g. # sqrt(x**(2*i)*y**(6*i)) -> x**i*y**(3**i) if x and y are positive; we # will take the log, expand it, and then factor out the common powers that # now appear as coefficient. We do this manually since terms_gcd pulls out # fractions, terms_gcd(x+x*y/2) -> x*(y + 2)/2 and we don't want the 1/2; # gcd won't pull out numerators from a fraction: gcd(3*x, 9*x/2) -> x but # we want 3*x. Neither work with noncommutatives. def nc_gcd(aa, bb): a, b = [i.as_coeff_Mul() for i in [aa, bb]] c = gcd(a[0], b[0]).as_numer_denom()[0] g = Mul(*(a[1].args_cnc(cset=True)[0] & b[1].args_cnc(cset=True)[0])) return _keep_coeff(c, g) glogb = expand_log(log(b)) if glogb.is_Add: args = glogb.args g = reduce(nc_gcd, args) if g != 1: cg, rg = g.as_coeff_Mul() glogb = _keep_coeff(cg, rg * Add(*[a / g for a in args])) # now put the log back together again if glogb.func is log or not glogb.is_Mul: if glogb.args[0].is_Pow or glogb.args[0].func is exp: glogb = _denest_pow(glogb.args[0]) if (abs(glogb.exp) < 1) == True: return Pow(glogb.base, glogb.exp * e) return eq # the log(b) was a Mul so join any adds with logcombine add = [] other = [] for a in glogb.args: if a.is_Add: add.append(a) else: other.append(a) return Pow(exp(logcombine(Mul(*add))), e * Mul(*other))
def as_coeff_matrices(self): scalars = [x for x in self.args if not x.is_Matrix] matrices = [x for x in self.args if x.is_Matrix] coeff = Mul(*scalars) return coeff, matrices
def apart(f, x=None, full=False, **options): """ Compute partial fraction decomposition of a rational function. Given a rational function ``f`` compute partial fraction decomposition of ``f``. Two algorithms are available: one is based on undetermined coefficients method and the other is Bronstein's full partial fraction decomposition algorithm. Examples ======== >>> from sympy.polys.partfrac import apart >>> from sympy.abc import x, y By default, using the undetermined coefficients method: >>> apart(y/(x + 2)/(x + 1), x) -y/(x + 2) + y/(x + 1) You can choose Bronstein's algorithm by setting ``full=True``: >>> apart(y/(x**2 + x + 1), x) y/(x**2 + x + 1) >>> apart(y/(x**2 + x + 1), x, full=True) RootSum(_w**2 + _w + 1, Lambda(_a, (-2*_a*y/3 - y/3)/(-_a + x))) See Also ======== apart_list, assemble_partfrac_list """ allowed_flags(options, []) f = sympify(f) if f.is_Atom: return f else: P, Q = f.as_numer_denom() _options = options.copy() options = set_defaults(options, extension=True) try: (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options) except PolynomialError as msg: if f.is_commutative: raise PolynomialError(msg) # non-commutative if f.is_Mul: c, nc = f.args_cnc(split_1=False) nc = Mul(*[apart(i, x=x, full=full, **_options) for i in nc]) if c: c = apart(Mul._from_args(c), x=x, full=full, **_options) return c*nc else: return nc elif f.is_Add: c = [] nc = [] for i in f.args: if i.is_commutative: c.append(i) else: try: nc.append(apart(i, x=x, full=full, **_options)) except NotImplementedError: nc.append(i) return apart(Add(*c), x=x, full=full, **_options) + Add(*nc) else: reps = [] pot = preorder_traversal(f) pot.next() for e in pot: try: reps.append((e, apart(e, x=x, full=full, **_options))) pot.skip() # this was handled successfully except NotImplementedError: pass return f.xreplace(dict(reps)) if P.is_multivariate: fc = f.cancel() if fc != f: return apart(fc, x=x, full=full, **_options) raise NotImplementedError( "multivariate partial fraction decomposition") common, P, Q = P.cancel(Q) poly, P = P.div(Q, auto=True) P, Q = P.rat_clear_denoms(Q) if Q.degree() <= 1: partial = P/Q else: if not full: partial = apart_undetermined_coeffs(P, Q) else: partial = apart_full_decomposition(P, Q) terms = S.Zero for term in Add.make_args(partial): if term.has(RootSum): terms += term else: terms += factor(term) return common*(poly.as_expr() + terms)
def from_index_summation(expr, first_index=None, last_index=None, dimensions=None): r""" Parse expression of matrices with explicitly summed indices into a matrix expression without indices, if possible. This transformation expressed in mathematical notation: `\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}` Optional parameter ``first_index``: specify which free index to use as the index starting the expression. Examples ======== >>> from sympy import MatrixSymbol, MatrixExpr, Sum, Symbol >>> from sympy.abc import i, j, k, l, N >>> A = MatrixSymbol("A", N, N) >>> B = MatrixSymbol("B", N, N) >>> expr = Sum(A[i, j]*B[j, k], (j, 0, N-1)) >>> MatrixExpr.from_index_summation(expr) A*B Transposition is detected: >>> expr = Sum(A[j, i]*B[j, k], (j, 0, N-1)) >>> MatrixExpr.from_index_summation(expr) A.T*B Detect the trace: >>> expr = Sum(A[i, i], (i, 0, N-1)) >>> MatrixExpr.from_index_summation(expr) Trace(A) More complicated expressions: >>> expr = Sum(A[i, j]*B[k, j]*A[l, k], (j, 0, N-1), (k, 0, N-1)) >>> MatrixExpr.from_index_summation(expr) A*B.T*A.T """ from sympy import Sum, Mul, Add, MatMul, transpose, trace from sympy.strategies.traverse import bottom_up def remove_matelement(expr, i1, i2): def repl_match(pos): def func(x): if not isinstance(x, MatrixElement): return False if x.args[pos] != i1: return False if x.args[3 - pos] == 0: if x.args[0].shape[2 - pos] == 1: return True else: return False return True return func expr = expr.replace(repl_match(1), lambda x: x.args[0]) expr = expr.replace(repl_match(2), lambda x: transpose(x.args[0])) # Make sure that all Mul are transformed to MatMul and that they # are flattened: rule = bottom_up(lambda x: reduce(lambda a, b: a * b, x.args) if isinstance(x, (Mul, MatMul)) else x) return rule(expr) def recurse_expr(expr, index_ranges={}): if expr.is_Mul: nonmatargs = [] pos_arg = [] pos_ind = [] dlinks = {} link_ind = [] counter = 0 args_ind = [] for arg in expr.args: retvals = recurse_expr(arg, index_ranges) assert isinstance(retvals, list) if isinstance(retvals, list): for i in retvals: args_ind.append(i) else: args_ind.append(retvals) for arg_symbol, arg_indices in args_ind: if arg_indices is None: nonmatargs.append(arg_symbol) continue if isinstance(arg_symbol, MatrixElement): arg_symbol = arg_symbol.args[0] pos_arg.append(arg_symbol) pos_ind.append(arg_indices) link_ind.append([None] * len(arg_indices)) for i, ind in enumerate(arg_indices): if ind in dlinks: other_i = dlinks[ind] link_ind[counter][i] = other_i link_ind[other_i[0]][other_i[1]] = (counter, i) dlinks[ind] = (counter, i) counter += 1 counter2 = 0 lines = {} while counter2 < len(link_ind): for i, e in enumerate(link_ind): if None in e: line_start_index = (i, e.index(None)) break cur_ind_pos = line_start_index cur_line = [] index1 = pos_ind[cur_ind_pos[0]][cur_ind_pos[1]] while True: d, r = cur_ind_pos if pos_arg[d] != 1: if r % 2 == 1: cur_line.append(transpose(pos_arg[d])) else: cur_line.append(pos_arg[d]) next_ind_pos = link_ind[d][1 - r] counter2 += 1 # Mark as visited, there will be no `None` anymore: link_ind[d] = (-1, -1) if next_ind_pos is None: index2 = pos_ind[d][1 - r] lines[(index1, index2)] = cur_line break cur_ind_pos = next_ind_pos lines = { k: MatMul.fromiter(v) if len(v) != 1 else v[0] for k, v in lines.items() } return [(Mul.fromiter(nonmatargs), None)] + [(MatrixElement( a, i, j), (i, j)) for (i, j), a in lines.items()] elif expr.is_Add: res = [recurse_expr(i) for i in expr.args] d = collections.defaultdict(list) for res_addend in res: scalar = 1 for elem, indices in res_addend: if indices is None: scalar = elem continue indices = tuple(sorted(indices, key=default_sort_key)) d[indices].append(scalar * remove_matelement(elem, *indices)) scalar = 1 return [(MatrixElement(Add.fromiter(v), *k), k) for k, v in d.items()] elif isinstance(expr, KroneckerDelta): i1, i2 = expr.args if dimensions is not None: identity = Identity(dimensions[0]) else: identity = S.One return [(MatrixElement(identity, i1, i2), (i1, i2))] elif isinstance(expr, MatrixElement): matrix_symbol, i1, i2 = expr.args if i1 in index_ranges: r1, r2 = index_ranges[i1] if r1 != 0 or matrix_symbol.shape[0] != r2 + 1: raise ValueError( "index range mismatch: {} vs. (0, {})".format( (r1, r2), matrix_symbol.shape[0])) if i2 in index_ranges: r1, r2 = index_ranges[i2] if r1 != 0 or matrix_symbol.shape[1] != r2 + 1: raise ValueError( "index range mismatch: {} vs. (0, {})".format( (r1, r2), matrix_symbol.shape[1])) if (i1 == i2) and (i1 in index_ranges): return [(trace(matrix_symbol), None)] return [(MatrixElement(matrix_symbol, i1, i2), (i1, i2))] elif isinstance(expr, Sum): return recurse_expr( expr.args[0], index_ranges={i[0]: i[1:] for i in expr.args[1:]}) else: return [(expr, None)] retvals = recurse_expr(expr) factors, indices = zip(*retvals) retexpr = Mul.fromiter(factors) if len(indices) == 0 or list(set(indices)) == [None]: return retexpr if first_index is None: for i in indices: if i is not None: ind0 = i break return remove_matelement(retexpr, *ind0) else: return remove_matelement(retexpr, first_index, last_index)
def heuristics(e, z, z0, dir): """Computes the limit of an expression term-wise. Parameters are the same as for the ``limit`` function. Works with the arguments of expression ``e`` one by one, computing the limit of each and then combining the results. This approach works only for simple limits, but it is fast. """ from sympy.calculus.util import AccumBounds rv = None if abs(z0) is S.Infinity: rv = limit(e.subs(z, 1 / z), z, S.Zero, "+" if z0 is S.Infinity else "-") if isinstance(rv, Limit): return elif e.is_Mul or e.is_Add or e.is_Pow or e.is_Function: r = [] for a in e.args: l = limit(a, z, z0, dir) if l.has(S.Infinity) and l.is_finite is None: if isinstance(e, Add): m = factor_terms(e) if not isinstance(m, Mul): # try together m = together(m) if not isinstance( m, Mul): # try factor if the previous methods failed m = factor(e) if isinstance(m, Mul): return heuristics(m, z, z0, dir) return return elif isinstance(l, Limit): return elif l is S.NaN: return else: r.append(l) if r: rv = e.func(*r) if rv is S.NaN and e.is_Mul and any( isinstance(rr, AccumBounds) for rr in r): r2 = [] e2 = [] for ii in range(len(r)): if isinstance(r[ii], AccumBounds): r2.append(r[ii]) else: e2.append(e.args[ii]) if len(e2) > 0: e3 = Mul(*e2).simplify() l = limit(e3, z, z0, dir) rv = l * Mul(*r2) if rv is S.NaN: try: rat_e = ratsimp(e) except PolynomialError: return if rat_e is S.NaN or rat_e == e: return return limit(rat_e, z, z0, dir) return rv
def simplify(expr, ratio=1.7, measure=count_ops, fu=False): """ Simplifies the given expression. Simplification is not a well defined term and the exact strategies this function tries can change in the future versions of SymPy. If your algorithm relies on "simplification" (whatever it is), try to determine what you need exactly - is it powsimp()?, radsimp()?, together()?, logcombine()?, or something else? And use this particular function directly, because those are well defined and thus your algorithm will be robust. Nonetheless, especially for interactive use, or when you don't know anything about the structure of the expression, simplify() tries to apply intelligent heuristics to make the input expression "simpler". For example: >>> from sympy import simplify, cos, sin >>> from sympy.abc import x, y >>> a = (x + x**2)/(x*sin(y)**2 + x*cos(y)**2) >>> a (x**2 + x)/(x*sin(y)**2 + x*cos(y)**2) >>> simplify(a) x + 1 Note that we could have obtained the same result by using specific simplification functions: >>> from sympy import trigsimp, cancel >>> trigsimp(a) (x**2 + x)/x >>> cancel(_) x + 1 In some cases, applying :func:`simplify` may actually result in some more complicated expression. The default ``ratio=1.7`` prevents more extreme cases: if (result length)/(input length) > ratio, then input is returned unmodified. The ``measure`` parameter lets you specify the function used to determine how complex an expression is. The function should take a single argument as an expression and return a number such that if expression ``a`` is more complex than expression ``b``, then ``measure(a) > measure(b)``. The default measure function is :func:`count_ops`, which returns the total number of operations in the expression. For example, if ``ratio=1``, ``simplify`` output can't be longer than input. :: >>> from sympy import sqrt, simplify, count_ops, oo >>> root = 1/(sqrt(2)+3) Since ``simplify(root)`` would result in a slightly longer expression, root is returned unchanged instead:: >>> simplify(root, ratio=1) == root True If ``ratio=oo``, simplify will be applied anyway:: >>> count_ops(simplify(root, ratio=oo)) > count_ops(root) True Note that the shortest expression is not necessary the simplest, so setting ``ratio`` to 1 may not be a good idea. Heuristically, the default value ``ratio=1.7`` seems like a reasonable choice. You can easily define your own measure function based on what you feel should represent the "size" or "complexity" of the input expression. Note that some choices, such as ``lambda expr: len(str(expr))`` may appear to be good metrics, but have other problems (in this case, the measure function may slow down simplify too much for very large expressions). If you don't know what a good metric would be, the default, ``count_ops``, is a good one. For example: >>> from sympy import symbols, log >>> a, b = symbols('a b', positive=True) >>> g = log(a) + log(b) + log(a)*log(1/b) >>> h = simplify(g) >>> h log(a*b**(-log(a) + 1)) >>> count_ops(g) 8 >>> count_ops(h) 5 So you can see that ``h`` is simpler than ``g`` using the count_ops metric. However, we may not like how ``simplify`` (in this case, using ``logcombine``) has created the ``b**(log(1/a) + 1)`` term. A simple way to reduce this would be to give more weight to powers as operations in ``count_ops``. We can do this by using the ``visual=True`` option: >>> print(count_ops(g, visual=True)) 2*ADD + DIV + 4*LOG + MUL >>> print(count_ops(h, visual=True)) 2*LOG + MUL + POW + SUB >>> from sympy import Symbol, S >>> def my_measure(expr): ... POW = Symbol('POW') ... # Discourage powers by giving POW a weight of 10 ... count = count_ops(expr, visual=True).subs(POW, 10) ... # Every other operation gets a weight of 1 (the default) ... count = count.replace(Symbol, type(S.One)) ... return count >>> my_measure(g) 8 >>> my_measure(h) 14 >>> 15./8 > 1.7 # 1.7 is the default ratio True >>> simplify(g, measure=my_measure) -log(a)*log(b) + log(a) + log(b) Note that because ``simplify()`` internally tries many different simplification strategies and then compares them using the measure function, we get a completely different result that is still different from the input expression by doing this. """ expr = sympify(expr) try: return expr._eval_simplify(ratio=ratio, measure=measure) except AttributeError: pass original_expr = expr = signsimp(expr) from sympy.simplify.hyperexpand import hyperexpand from sympy.functions.special.bessel import BesselBase from sympy import Sum, Product if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack return expr if not isinstance(expr, (Add, Mul, Pow, ExpBase)): if isinstance(expr, Function) and hasattr(expr, "inverse"): if len(expr.args) == 1 and len(expr.args[0].args) == 1 and \ isinstance(expr.args[0], expr.inverse(argindex=1)): return simplify(expr.args[0].args[0], ratio=ratio, measure=measure, fu=fu) return expr.func(*[simplify(x, ratio=ratio, measure=measure, fu=fu) for x in expr.args]) # TODO: Apply different strategies, considering expression pattern: # is it a purely rational function? Is there any trigonometric function?... # See also https://github.com/sympy/sympy/pull/185. def shorter(*choices): '''Return the choice that has the fewest ops. In case of a tie, the expression listed first is selected.''' if not has_variety(choices): return choices[0] return min(choices, key=measure) expr = bottom_up(expr, lambda w: w.normal()) expr = Mul(*powsimp(expr).as_content_primitive()) _e = cancel(expr) expr1 = shorter(_e, _mexpand(_e).cancel()) # issue 6829 expr2 = shorter(together(expr, deep=True), together(expr1, deep=True)) if ratio is S.Infinity: expr = expr2 else: expr = shorter(expr2, expr1, expr) if not isinstance(expr, Basic): # XXX: temporary hack return expr expr = factor_terms(expr, sign=False) # hyperexpand automatically only works on hypergeometric terms expr = hyperexpand(expr) expr = piecewise_fold(expr) if expr.has(BesselBase): expr = besselsimp(expr) if expr.has(TrigonometricFunction) and not fu or expr.has( HyperbolicFunction): expr = trigsimp(expr, deep=True) if expr.has(log): expr = shorter(expand_log(expr, deep=True), logcombine(expr)) if expr.has(CombinatorialFunction, gamma): expr = combsimp(expr) if expr.has(Sum): expr = sum_simplify(expr) if expr.has(Product): expr = product_simplify(expr) short = shorter(powsimp(expr, combine='exp', deep=True), powsimp(expr), expr) short = shorter(short, factor_terms(short), expand_power_exp(expand_mul(short))) if short.has(TrigonometricFunction, HyperbolicFunction, ExpBase): short = exptrigsimp(short, simplify=False) # get rid of hollow 2-arg Mul factorization hollow_mul = Transform( lambda x: Mul(*x.args), lambda x: x.is_Mul and len(x.args) == 2 and x.args[0].is_Number and x.args[1].is_Add and x.is_commutative) expr = short.xreplace(hollow_mul) numer, denom = expr.as_numer_denom() if denom.is_Add: n, d = fraction(radsimp(1/denom, symbolic=False, max_terms=1)) if n is not S.One: expr = (numer*n).expand()/d if expr.could_extract_minus_sign(): n, d = fraction(expr) if d != 0: expr = signsimp(-n/(-d)) if measure(expr) > ratio*measure(original_expr): expr = original_expr return expr
def _sqrt_match(p): """Return [a, b, r] for p.match(a + b*sqrt(r)) where, in addition to matching, sqrt(r) also has then maximal sqrt_depth among addends of p. Examples ======== >>> from sympy.functions.elementary.miscellaneous import sqrt >>> from sympy.simplify.sqrtdenest import _sqrt_match >>> _sqrt_match(1 + sqrt(2) + sqrt(2)*sqrt(3) + 2*sqrt(1+sqrt(5))) [1 + sqrt(2) + sqrt(6), 2, 1 + sqrt(5)] """ from sympy.simplify.simplify import split_surds p = _mexpand(p) if p.is_Number: res = (p, S.Zero, S.Zero) elif p.is_Add: pargs = sorted(p.args, key=default_sort_key) if all((x**2).is_Rational for x in pargs): r, b, a = split_surds(p) res = a, b, r return list(res) # to make the process canonical, the argument is included in the tuple # so when the max is selected, it will be the largest arg having a # given depth v = [(sqrt_depth(x), x, i) for i, x in enumerate(pargs)] nmax = max(v, key=default_sort_key) if nmax[0] == 0: res = [] else: # select r depth, _, i = nmax r = pargs.pop(i) v.pop(i) b = S.One if r.is_Mul: bv = [] rv = [] for x in r.args: if sqrt_depth(x) < depth: bv.append(x) else: rv.append(x) b = Mul._from_args(bv) r = Mul._from_args(rv) # collect terms comtaining r a1 = [] b1 = [b] for x in v: if x[0] < depth: a1.append(x[1]) else: x1 = x[1] if x1 == r: b1.append(1) else: if x1.is_Mul: x1args = list(x1.args) if r in x1args: x1args.remove(r) b1.append(Mul(*x1args)) else: a1.append(x[1]) else: a1.append(x[1]) a = Add(*a1) b = Add(*b1) res = (a, b, r**2) else: b, r = p.as_coeff_Mul() if is_sqrt(r): res = (S.Zero, b, r**2) else: res = [] return list(res)
def repl(nu, z): if factors.intersection(Mul.make_args(z)): return to(nu, z) return fro(nu, z)
def powsimp(expr, deep=False, combine='all', force=False, measure=count_ops): """ reduces expression by combining powers with similar bases and exponents. Notes ===== If deep is True then powsimp() will also simplify arguments of functions. By default deep is set to False. If force is True then bases will be combined without checking for assumptions, e.g. sqrt(x)*sqrt(y) -> sqrt(x*y) which is not true if x and y are both negative. You can make powsimp() only combine bases or only combine exponents by changing combine='base' or combine='exp'. By default, combine='all', which does both. combine='base' will only combine:: a a a 2x x x * y => (x*y) as well as things like 2 => 4 and combine='exp' will only combine :: a b (a + b) x * x => x combine='exp' will strictly only combine exponents in the way that used to be automatic. Also use deep=True if you need the old behavior. When combine='all', 'exp' is evaluated first. Consider the first example below for when there could be an ambiguity relating to this. This is done so things like the second example can be completely combined. If you want 'base' combined first, do something like powsimp(powsimp(expr, combine='base'), combine='exp'). Examples ======== >>> from sympy import powsimp, exp, log, symbols >>> from sympy.abc import x, y, z, n >>> powsimp(x**y*x**z*y**z, combine='all') x**(y + z)*y**z >>> powsimp(x**y*x**z*y**z, combine='exp') x**(y + z)*y**z >>> powsimp(x**y*x**z*y**z, combine='base', force=True) x**y*(x*y)**z >>> powsimp(x**z*x**y*n**z*n**y, combine='all', force=True) (n*x)**(y + z) >>> powsimp(x**z*x**y*n**z*n**y, combine='exp') n**(y + z)*x**(y + z) >>> powsimp(x**z*x**y*n**z*n**y, combine='base', force=True) (n*x)**y*(n*x)**z >>> x, y = symbols('x y', positive=True) >>> powsimp(log(exp(x)*exp(y))) log(exp(x)*exp(y)) >>> powsimp(log(exp(x)*exp(y)), deep=True) x + y Radicals with Mul bases will be combined if combine='exp' >>> from sympy import sqrt, Mul >>> x, y = symbols('x y') Two radicals are automatically joined through Mul: >>> a=sqrt(x*sqrt(y)) >>> a*a**3 == a**4 True But if an integer power of that radical has been autoexpanded then Mul does not join the resulting factors: >>> a**4 # auto expands to a Mul, no longer a Pow x**2*y >>> _*a # so Mul doesn't combine them x**2*y*sqrt(x*sqrt(y)) >>> powsimp(_) # but powsimp will (x*sqrt(y))**(5/2) >>> powsimp(x*y*a) # but won't when doing so would violate assumptions x*y*sqrt(x*sqrt(y)) """ from sympy.matrices.expressions.matexpr import MatrixSymbol def recurse(arg, **kwargs): _deep = kwargs.get('deep', deep) _combine = kwargs.get('combine', combine) _force = kwargs.get('force', force) _measure = kwargs.get('measure', measure) return powsimp(arg, _deep, _combine, _force, _measure) expr = sympify(expr) if (not isinstance(expr, Basic) or isinstance(expr, MatrixSymbol) or (expr.is_Atom or expr in (exp_polar(0), exp_polar(1)))): return expr if deep or expr.is_Add or expr.is_Mul and _y not in expr.args: expr = expr.func(*[recurse(w) for w in expr.args]) if expr.is_Pow: return recurse(expr * _y, deep=False) / _y if not expr.is_Mul: return expr # handle the Mul if combine in ('exp', 'all'): # Collect base/exp data, while maintaining order in the # non-commutative parts of the product c_powers = defaultdict(list) nc_part = [] newexpr = [] coeff = S.One for term in expr.args: if term.is_Rational: coeff *= term continue if term.is_Pow: term = _denest_pow(term) if term.is_commutative: b, e = term.as_base_exp() if deep: b, e = [recurse(i) for i in [b, e]] if b.is_Pow or b.func is exp: # don't let smthg like sqrt(x**a) split into x**a, 1/2 # or else it will be joined as x**(a/2) later b, e = b**e, S.One c_powers[b].append(e) else: # This is the logic that combines exponents for equal, # but non-commutative bases: A**x*A**y == A**(x+y). if nc_part: b1, e1 = nc_part[-1].as_base_exp() b2, e2 = term.as_base_exp() if (b1 == b2 and e1.is_commutative and e2.is_commutative): nc_part[-1] = Pow(b1, Add(e1, e2)) continue nc_part.append(term) # add up exponents of common bases for b, e in ordered(iter(c_powers.items())): # allow 2**x/4 -> 2**(x - 2); don't do this when b and e are # Numbers since autoevaluation will undo it, e.g. # 2**(1/3)/4 -> 2**(1/3 - 2) -> 2**(1/3)/4 if (b and b.is_Number and not all(ei.is_Number for ei in e) and \ coeff is not S.One and b not in (S.One, S.NegativeOne)): m = multiplicity(abs(b), abs(coeff)) if m: e.append(m) coeff /= b**m c_powers[b] = Add(*e) if coeff is not S.One: if coeff in c_powers: c_powers[coeff] += S.One else: c_powers[coeff] = S.One # convert to plain dictionary c_powers = dict(c_powers) # check for base and inverted base pairs be = list(c_powers.items()) skip = set() # skip if we already saw them for b, e in be: if b in skip: continue bpos = b.is_positive or b.is_polar if bpos: binv = 1 / b if b != binv and binv in c_powers: if b.as_numer_denom()[0] is S.One: c_powers.pop(b) c_powers[binv] -= e else: skip.add(binv) e = c_powers.pop(binv) c_powers[b] -= e # check for base and negated base pairs be = list(c_powers.items()) _n = S.NegativeOne for i, (b, e) in enumerate(be): if ((-b).is_Symbol or b.is_Add) and -b in c_powers: if (b.is_positive in (0, 1) or e.is_integer): c_powers[-b] += c_powers.pop(b) if _n in c_powers: c_powers[_n] += e else: c_powers[_n] = e # filter c_powers and convert to a list c_powers = [(b, e) for b, e in c_powers.items() if e] # ============================================================== # check for Mul bases of Rational powers that can be combined with # separated bases, e.g. x*sqrt(x*y)*sqrt(x*sqrt(x*y)) -> # (x*sqrt(x*y))**(3/2) # ---------------- helper functions def ratq(x): '''Return Rational part of x's exponent as it appears in the bkey. ''' return bkey(x)[0][1] def bkey(b, e=None): '''Return (b**s, c.q), c.p where e -> c*s. If e is not given then it will be taken by using as_base_exp() on the input b. e.g. x**3/2 -> (x, 2), 3 x**y -> (x**y, 1), 1 x**(2*y/3) -> (x**y, 3), 2 exp(x/2) -> (exp(a), 2), 1 ''' if e is not None: # coming from c_powers or from below if e.is_Integer: return (b, S.One), e elif e.is_Rational: return (b, Integer(e.q)), Integer(e.p) else: c, m = e.as_coeff_Mul(rational=True) if c is not S.One: return (b**m, Integer(c.q)), Integer(c.p) else: return (b**e, S.One), S.One else: return bkey(*b.as_base_exp()) def update(b): '''Decide what to do with base, b. If its exponent is now an integer multiple of the Rational denominator, then remove it and put the factors of its base in the common_b dictionary or update the existing bases if necessary. If it has been zeroed out, simply remove the base. ''' newe, r = divmod(common_b[b], b[1]) if not r: common_b.pop(b) if newe: for m in Mul.make_args(b[0]**newe): b, e = bkey(m) if b not in common_b: common_b[b] = 0 common_b[b] += e if b[1] != 1: bases.append(b) # ---------------- end of helper functions # assemble a dictionary of the factors having a Rational power common_b = {} done = [] bases = [] for b, e in c_powers: b, e = bkey(b, e) if b in common_b.keys(): common_b[b] = common_b[b] + e else: common_b[b] = e if b[1] != 1 and b[0].is_Mul: bases.append(b) c_powers = [(b, e) for b, e in common_b.items() if e] bases.sort(key=default_sort_key) # this makes tie-breaking canonical bases.sort(key=measure, reverse=True) # handle longest first for base in bases: if base not in common_b: # it may have been removed already continue b, exponent = base last = False # True when no factor of base is a radical qlcm = 1 # the lcm of the radical denominators while True: bstart = b qstart = qlcm bb = [] # list of factors ee = [] # (factor's expo. and it's current value in common_b) for bi in Mul.make_args(b): bib, bie = bkey(bi) if bib not in common_b or common_b[bib] < bie: ee = bb = [] # failed break ee.append([bie, common_b[bib]]) bb.append(bib) if ee: # find the number of extractions possible # e.g. [(1, 2), (2, 2)] -> min(2/1, 2/2) -> 1 min1 = ee[0][1] / ee[0][0] for i in range(len(ee)): rat = ee[i][1] / ee[i][0] if rat < 1: break min1 = min(min1, rat) else: # update base factor counts # e.g. if ee = [(2, 5), (3, 6)] then min1 = 2 # and the new base counts will be 5-2*2 and 6-2*3 for i in range(len(bb)): common_b[bb[i]] -= min1 * ee[i][0] update(bb[i]) # update the count of the base # e.g. x**2*y*sqrt(x*sqrt(y)) the count of x*sqrt(y) # will increase by 4 to give bkey (x*sqrt(y), 2, 5) common_b[base] += min1 * qstart * exponent if (last # no more radicals in base or len(common_b) == 1 # nothing left to join with or all(k[1] == 1 for k in common_b) # no rad's in common_b ): break # see what we can exponentiate base by to remove any radicals # so we know what to search for # e.g. if base were x**(1/2)*y**(1/3) then we should # exponentiate by 6 and look for powers of x and y in the ratio # of 2 to 3 qlcm = lcm([ratq(bi) for bi in Mul.make_args(bstart)]) if qlcm == 1: break # we are done b = bstart**qlcm qlcm *= qstart if all(ratq(bi) == 1 for bi in Mul.make_args(b)): last = True # we are going to be done after this next pass # this base no longer can find anything to join with and # since it was longer than any other we are done with it b, q = base done.append((b, common_b.pop(base) * Rational(1, q))) # update c_powers and get ready to continue with powsimp c_powers = done # there may be terms still in common_b that were bases that were # identified as needing processing, so remove those, too for (b, q), e in common_b.items(): if (b.is_Pow or b.func is exp) and \ q is not S.One and not b.exp.is_Rational: b, be = b.as_base_exp() b = b**(be / q) else: b = root(b, q) c_powers.append((b, e)) check = len(c_powers) c_powers = dict(c_powers) assert len(c_powers) == check # there should have been no duplicates # ============================================================== # rebuild the expression newexpr = expr.func(*(newexpr + [Pow(b, e) for b, e in c_powers.items()])) if combine == 'exp': return expr.func(newexpr, expr.func(*nc_part)) else: return recurse(expr.func(*nc_part), combine='base') * \ recurse(newexpr, combine='base') elif combine == 'base': # Build c_powers and nc_part. These must both be lists not # dicts because exp's are not combined. c_powers = [] nc_part = [] for term in expr.args: if term.is_commutative: c_powers.append(list(term.as_base_exp())) else: # This is the logic that combines bases that are # different and non-commutative, but with equal and # commutative exponents: A**x*B**x == (A*B)**x. if nc_part: b1, e1 = nc_part[-1].as_base_exp() b2, e2 = term.as_base_exp() if (e1 == e2 and e2.is_commutative): nc_part[-1] = Pow(b1 * b2, e1) continue nc_part.append(term) # Pull out numerical coefficients from exponent if assumptions allow # e.g., 2**(2*x) => 4**x for i in range(len(c_powers)): b, e = c_powers[i] if not (all(x.is_nonnegative for x in b.as_numer_denom()) or e.is_integer or force or b.is_polar): continue exp_c, exp_t = e.as_coeff_Mul(rational=True) if exp_c is not S.One and exp_t is not S.One: c_powers[i] = [Pow(b, exp_c), exp_t] # Combine bases whenever they have the same exponent and # assumptions allow # first gather the potential bases under the common exponent c_exp = defaultdict(list) for b, e in c_powers: if deep: e = recurse(e) c_exp[e].append(b) del c_powers # Merge back in the results of the above to form a new product c_powers = defaultdict(list) for e in c_exp: bases = c_exp[e] # calculate the new base for e if len(bases) == 1: new_base = bases[0] elif e.is_integer or force: new_base = expr.func(*bases) else: # see which ones can be joined unk = [] nonneg = [] neg = [] for bi in bases: if bi.is_negative: neg.append(bi) elif bi.is_nonnegative: nonneg.append(bi) elif bi.is_polar: nonneg.append( bi) # polar can be treated like non-negative else: unk.append(bi) if len(unk) == 1 and not neg or len(neg) == 1 and not unk: # a single neg or a single unk can join the rest nonneg.extend(unk + neg) unk = neg = [] elif neg: # their negative signs cancel in groups of 2*q if we know # that e = p/q else we have to treat them as unknown israt = False if e.is_Rational: israt = True else: p, d = e.as_numer_denom() if p.is_integer and d.is_integer: israt = True if israt: neg = [-w for w in neg] unk.extend([S.NegativeOne] * len(neg)) else: unk.extend(neg) neg = [] del israt # these shouldn't be joined for b in unk: c_powers[b].append(e) # here is a new joined base new_base = expr.func(*(nonneg + neg)) # if there are positive parts they will just get separated # again unless some change is made def _terms(e): # return the number of terms of this expression # when multiplied out -- assuming no joining of terms if e.is_Add: return sum([_terms(ai) for ai in e.args]) if e.is_Mul: return prod([_terms(mi) for mi in e.args]) return 1 xnew_base = expand_mul(new_base, deep=False) if len(Add.make_args(xnew_base)) < _terms(new_base): new_base = factor_terms(xnew_base) c_powers[new_base].append(e) # break out the powers from c_powers now c_part = [Pow(b, ei) for b, e in c_powers.items() for ei in e] # we're done return expr.func(*(c_part + nc_part)) else: raise ValueError("combine must be one of ('all', 'exp', 'base').")
def collect_const(expr, *vars, Numbers=True): """A non-greedy collection of terms with similar number coefficients in an Add expr. If ``vars`` is given then only those constants will be targeted. Although any Number can also be targeted, if this is not desired set ``Numbers=False`` and no Float or Rational will be collected. Parameters ========== expr : sympy expression This parameter defines the expression the expression from which terms with similar coefficients are to be collected. A non-Add expression is returned as it is. vars : variable length collection of Numbers, optional Specifies the constants to target for collection. Can be multiple in number. Numbers : bool Specifies to target all instance of :class:`sympy.core.numbers.Number` class. If ``Numbers=False``, then no Float or Rational will be collected. Returns ======= expr : Expr Returns an expression with similar coefficient terms collected. Examples ======== >>> from sympy import sqrt >>> from sympy.abc import s, x, y, z >>> from sympy.simplify.radsimp import collect_const >>> collect_const(sqrt(3) + sqrt(3)*(1 + sqrt(2))) sqrt(3)*(sqrt(2) + 2) >>> collect_const(sqrt(3)*s + sqrt(7)*s + sqrt(3) + sqrt(7)) (sqrt(3) + sqrt(7))*(s + 1) >>> s = sqrt(2) + 2 >>> collect_const(sqrt(3)*s + sqrt(3) + sqrt(7)*s + sqrt(7)) (sqrt(2) + 3)*(sqrt(3) + sqrt(7)) >>> collect_const(sqrt(3)*s + sqrt(3) + sqrt(7)*s + sqrt(7), sqrt(3)) sqrt(7) + sqrt(3)*(sqrt(2) + 3) + sqrt(7)*(sqrt(2) + 2) The collection is sign-sensitive, giving higher precedence to the unsigned values: >>> collect_const(x - y - z) x - (y + z) >>> collect_const(-y - z) -(y + z) >>> collect_const(2*x - 2*y - 2*z, 2) 2*(x - y - z) >>> collect_const(2*x - 2*y - 2*z, -2) 2*x - 2*(y + z) See Also ======== collect, collect_sqrt, rcollect """ if not expr.is_Add: return expr recurse = False if not vars: recurse = True vars = set() for a in expr.args: for m in Mul.make_args(a): if m.is_number: vars.add(m) else: vars = sympify(vars) if not Numbers: vars = [v for v in vars if not v.is_Number] vars = list(ordered(vars)) for v in vars: terms = defaultdict(list) Fv = Factors(v) for m in Add.make_args(expr): f = Factors(m) q, r = f.div(Fv) if r.is_one: # only accept this as a true factor if # it didn't change an exponent from an Integer # to a non-Integer, e.g. 2/sqrt(2) -> sqrt(2) # -- we aren't looking for this sort of change fwas = f.factors.copy() fnow = q.factors if not any(k in fwas and fwas[k].is_Integer and not fnow[k].is_Integer for k in fnow): terms[v].append(q.as_expr()) continue terms[S.One].append(m) args = [] hit = False uneval = False for k in ordered(terms): v = terms[k] if k is S.One: args.extend(v) continue if len(v) > 1: v = Add(*v) hit = True if recurse and v != expr: vars.append(v) else: v = v[0] # be careful not to let uneval become True unless # it must be because it's going to be more expensive # to rebuild the expression as an unevaluated one if Numbers and k.is_Number and v.is_Add: args.append(_keep_coeff(k, v, sign=True)) uneval = True else: args.append(k*v) if hit: if uneval: expr = _unevaluated_Add(*args) else: expr = Add(*args) if not expr.is_Add: break return expr
def simplify(expr, ratio=1.7, measure=count_ops, fu=False): """ Simplifies the given expression. Simplification is not a well defined term and the exact strategies this function tries can change in the future versions of SymPy. If your algorithm relies on "simplification" (whatever it is), try to determine what you need exactly - is it powsimp()?, radsimp()?, together()?, logcombine()?, or something else? And use this particular function directly, because those are well defined and thus your algorithm will be robust. Nonetheless, especially for interactive use, or when you don't know anything about the structure of the expression, simplify() tries to apply intelligent heuristics to make the input expression "simpler". For example: >>> from sympy import simplify, cos, sin >>> from sympy.abc import x, y >>> a = (x + x**2)/(x*sin(y)**2 + x*cos(y)**2) >>> a (x**2 + x)/(x*sin(y)**2 + x*cos(y)**2) >>> simplify(a) x + 1 Note that we could have obtained the same result by using specific simplification functions: >>> from sympy import trigsimp, cancel >>> trigsimp(a) (x**2 + x)/x >>> cancel(_) x + 1 In some cases, applying :func:`simplify` may actually result in some more complicated expression. The default ``ratio=1.7`` prevents more extreme cases: if (result length)/(input length) > ratio, then input is returned unmodified. The ``measure`` parameter lets you specify the function used to determine how complex an expression is. The function should take a single argument as an expression and return a number such that if expression ``a`` is more complex than expression ``b``, then ``measure(a) > measure(b)``. The default measure function is :func:`count_ops`, which returns the total number of operations in the expression. For example, if ``ratio=1``, ``simplify`` output can't be longer than input. :: >>> from sympy import sqrt, simplify, count_ops, oo >>> root = 1/(sqrt(2)+3) Since ``simplify(root)`` would result in a slightly longer expression, root is returned unchanged instead:: >>> simplify(root, ratio=1) == root True If ``ratio=oo``, simplify will be applied anyway:: >>> count_ops(simplify(root, ratio=oo)) > count_ops(root) True Note that the shortest expression is not necessary the simplest, so setting ``ratio`` to 1 may not be a good idea. Heuristically, the default value ``ratio=1.7`` seems like a reasonable choice. You can easily define your own measure function based on what you feel should represent the "size" or "complexity" of the input expression. Note that some choices, such as ``lambda expr: len(str(expr))`` may appear to be good metrics, but have other problems (in this case, the measure function may slow down simplify too much for very large expressions). If you don't know what a good metric would be, the default, ``count_ops``, is a good one. For example: >>> from sympy import symbols, log >>> a, b = symbols('a b', positive=True) >>> g = log(a) + log(b) + log(a)*log(1/b) >>> h = simplify(g) >>> h log(a*b**(-log(a) + 1)) >>> count_ops(g) 8 >>> count_ops(h) 5 So you can see that ``h`` is simpler than ``g`` using the count_ops metric. However, we may not like how ``simplify`` (in this case, using ``logcombine``) has created the ``b**(log(1/a) + 1)`` term. A simple way to reduce this would be to give more weight to powers as operations in ``count_ops``. We can do this by using the ``visual=True`` option: >>> print(count_ops(g, visual=True)) 2*ADD + DIV + 4*LOG + MUL >>> print(count_ops(h, visual=True)) 2*LOG + MUL + POW + SUB >>> from sympy import Symbol, S >>> def my_measure(expr): ... POW = Symbol('POW') ... # Discourage powers by giving POW a weight of 10 ... count = count_ops(expr, visual=True).subs(POW, 10) ... # Every other operation gets a weight of 1 (the default) ... count = count.replace(Symbol, type(S.One)) ... return count >>> my_measure(g) 8 >>> my_measure(h) 14 >>> 15./8 > 1.7 # 1.7 is the default ratio True >>> simplify(g, measure=my_measure) -log(a)*log(b) + log(a) + log(b) Note that because ``simplify()`` internally tries many different simplification strategies and then compares them using the measure function, we get a completely different result that is still different from the input expression by doing this. """ expr = sympify(expr) try: return expr._eval_simplify(ratio=ratio, measure=measure) except AttributeError: pass original_expr = expr = signsimp(expr) from sympy.simplify.hyperexpand import hyperexpand from sympy.functions.special.bessel import BesselBase from sympy import Sum, Product if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack return expr if not isinstance(expr, (Add, Mul, Pow, ExpBase)): if isinstance(expr, Function) and hasattr(expr, "inverse"): if len(expr.args) == 1 and len(expr.args[0].args) == 1 and \ isinstance(expr.args[0], expr.inverse(argindex=1)): return simplify(expr.args[0].args[0], ratio=ratio, measure=measure, fu=fu) return expr.func(*[simplify(x, ratio=ratio, measure=measure, fu=fu) for x in expr.args]) # TODO: Apply different strategies, considering expression pattern: # is it a purely rational function? Is there any trigonometric function?... # See also https://github.com/sympy/sympy/pull/185. def shorter(*choices): '''Return the choice that has the fewest ops. In case of a tie, the expression listed first is selected.''' if not has_variety(choices): return choices[0] return min(choices, key=measure) expr = bottom_up(expr, lambda w: w.normal()) expr = Mul(*powsimp(expr).as_content_primitive()) _e = cancel(expr) expr1 = shorter(_e, _mexpand(_e).cancel()) # issue 6829 expr2 = shorter(together(expr, deep=True), together(expr1, deep=True)) if ratio is S.Infinity: expr = expr2 else: expr = shorter(expr2, expr1, expr) if not isinstance(expr, Basic): # XXX: temporary hack return expr expr = factor_terms(expr, sign=False) # hyperexpand automatically only works on hypergeometric terms expr = hyperexpand(expr) expr = piecewise_fold(expr) if expr.has(BesselBase): expr = besselsimp(expr) if expr.has(TrigonometricFunction) and not fu or expr.has( HyperbolicFunction): expr = trigsimp(expr, deep=True) if expr.has(log): expr = shorter(expand_log(expr, deep=True), logcombine(expr)) if expr.has(CombinatorialFunction, gamma): expr = combsimp(expr) if expr.has(Sum): expr = sum_simplify(expr) if expr.has(Product): expr = product_simplify(expr) short = shorter(powsimp(expr, combine='exp', deep=True), powsimp(expr), expr) short = shorter(short, factor_terms(short), expand_power_exp(expand_mul(short))) if short.has(TrigonometricFunction, HyperbolicFunction, ExpBase): short = exptrigsimp(short, simplify=False) # get rid of hollow 2-arg Mul factorization hollow_mul = Transform( lambda x: Mul(*x.args), lambda x: x.is_Mul and len(x.args) == 2 and x.args[0].is_Number and x.args[1].is_Add and x.is_commutative) expr = short.xreplace(hollow_mul) numer, denom = expr.as_numer_denom() if denom.is_Add: n, d = fraction(radsimp(1/denom, symbolic=False, max_terms=1)) if n is not S.One: expr = (numer*n).expand()/d if expr.could_extract_minus_sign(): n, d = fraction(expr) if d != 0: expr = signsimp(-n/(-d)) if measure(expr) > ratio*measure(original_expr): expr = original_expr return expr
def _parallel_dict_from_expr_no_gens(exprs, opt): """Transform expressions into a multinomial form and figure out generators. """ if opt.domain is not None: def _is_coeff(factor): return factor in opt.domain elif opt.extension is True: def _is_coeff(factor): return factor.is_algebraic elif opt.greedy is not False: def _is_coeff(factor): return False else: def _is_coeff(factor): return factor.is_number gens, reprs = set([]), [] for expr in exprs: terms = [] if expr.is_Equality: expr = expr.lhs - expr.rhs for term in Add.make_args(expr): coeff, elements = [], {} for factor in Mul.make_args(term): if not _not_a_coeff(factor) and (factor.is_Number or _is_coeff(factor)): coeff.append(factor) else: if opt.series is False: base, exp = decompose_power(factor) if exp < 0: exp, base = -exp, Pow(base, -S.One) else: base, exp = decompose_power_rat(factor) elements[base] = elements.setdefault(base, 0) + exp gens.add(base) terms.append((coeff, elements)) reprs.append(terms) gens = _sort_gens(gens, opt=opt) k, indices = len(gens), {} for i, g in enumerate(gens): indices[g] = i polys = [] for terms in reprs: poly = {} for coeff, term in terms: monom = [0] * k for base, exp in term.items(): monom[indices[base]] = exp monom = tuple(monom) if monom in poly: poly[monom] += Mul(*coeff) else: poly[monom] = Mul(*coeff) polys.append(poly) return polys, tuple(gens)
def _denester(nested, av0, h, max_depth_level): """Denests a list of expressions that contain nested square roots. Algorithm based on <http://www.almaden.ibm.com/cs/people/fagin/symb85.pdf>. It is assumed that all of the elements of 'nested' share the same bottom-level radicand. (This is stated in the paper, on page 177, in the paragraph immediately preceding the algorithm.) When evaluating all of the arguments in parallel, the bottom-level radicand only needs to be denested once. This means that calling _denester with x arguments results in a recursive invocation with x+1 arguments; hence _denester has polynomial complexity. However, if the arguments were evaluated separately, each call would result in two recursive invocations, and the algorithm would have exponential complexity. This is discussed in the paper in the middle paragraph of page 179. """ from sympy.simplify.simplify import radsimp if h > max_depth_level: return None, None if av0[1] is None: return None, None if (av0[0] is None and all(n.is_Number for n in nested)): # no arguments are nested for f in _subsets(len(nested)): # test subset 'f' of nested p = _mexpand(Mul(*[nested[i] for i in range(len(f)) if f[i]])) if f.count(1) > 1 and f[-1]: p = -p sqp = sqrt(p) if sqp.is_Rational: return sqp, f # got a perfect square so return its square root. # Otherwise, return the radicand from the previous invocation. return sqrt(nested[-1]), [0] * len(nested) else: R = None if av0[0] is not None: values = [av0[:2]] R = av0[2] nested2 = [av0[3], R] av0[0] = None else: values = list(filter(None, [_sqrt_match(expr) for expr in nested])) for v in values: if v[2]: # Since if b=0, r is not defined if R is not None: if R != v[2]: av0[1] = None return None, None else: R = v[2] if R is None: # return the radicand from the previous invocation return sqrt(nested[-1]), [0] * len(nested) nested2 = [ _mexpand(v[0]**2) - _mexpand(R * v[1]**2) for v in values ] + [R] d, f = _denester(nested2, av0, h + 1, max_depth_level) if not f: return None, None if not any(f[i] for i in range(len(nested))): v = values[-1] return sqrt(v[0] + _mexpand(v[1] * d)), f else: p = Mul(*[nested[i] for i in range(len(nested)) if f[i]]) v = _sqrt_match(p) if 1 in f and f.index(1) < len(nested) - 1 and f[len(nested) - 1]: v[0] = -v[0] v[1] = -v[1] if not f[len(nested)]: # Solution denests with square roots vad = _mexpand(v[0] + d) if vad <= 0: # return the radicand from the previous invocation. return sqrt(nested[-1]), [0] * len(nested) if not (sqrt_depth(vad) <= sqrt_depth(R) + 1 or (vad**2).is_Number): av0[1] = None return None, None sqvad = _sqrtdenest1(sqrt(vad), denester=False) if not (sqrt_depth(sqvad) <= sqrt_depth(R) + 1): av0[1] = None return None, None sqvad1 = radsimp(1 / sqvad) res = _mexpand(sqvad / sqrt(2) + (v[1] * sqrt(R) * sqvad1 / sqrt(2))) return res, f # sign(v[1])*sqrt(_mexpand(v[1]**2*R*vad1/2))), f else: # Solution requires a fourth root s2 = _mexpand(v[1] * R) + d if s2 <= 0: return sqrt(nested[-1]), [0] * len(nested) FR, s = root(_mexpand(R), 4), sqrt(s2) return _mexpand(s / (sqrt(2) * FR) + v[0] * FR / (sqrt(2) * s)), f
def _print_Mul(self, expr): # print complex numbers nicely in Octave if expr.is_number and expr.is_imaginary and expr.as_coeff_Mul()[0].is_integer: return "%si" % self._print(-S.ImaginaryUnit * expr) # cribbed from str.py prec = precedence(expr) c, e = expr.as_coeff_Mul() if c < 0: expr = _keep_coeff(-c, e) sign = "-" else: sign = "" a = [] # items in the numerator b = [] # items that are in the denominator (if any) if self.order not in ("old", "none"): args = expr.as_ordered_factors() else: # use make_args in case expr was something like -x -> x args = Mul.make_args(expr) # Gather args for numerator/denominator for item in args: if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative: if item.exp != -1: b.append(Pow(item.base, -item.exp, evaluate=False)) else: b.append(Pow(item.base, -item.exp)) elif item.is_Rational and item is not S.Infinity: if item.p != 1: a.append(Rational(item.p)) if item.q != 1: b.append(Rational(item.q)) else: a.append(item) a = a or [S.One] a_str = list(map(lambda x: self.parenthesize(x, prec), a)) b_str = list(map(lambda x: self.parenthesize(x, prec), b)) # from here it differs from str.py to deal with "*" and ".*" def multjoin(a, a_str): # here we probably are assuming the constants will come first r = a_str[0] for i in range(1, len(a)): mulsym = "*" if a[i - 1].is_number else ".*" r = r + mulsym + a_str[i] return r if len(b) == 0: return sign + multjoin(a, a_str) elif len(b) == 1: divsym = "/" if b[0].is_number else "./" return sign + multjoin(a, a_str) + divsym + b_str[0] else: divsym = "/" if all([bi.is_number for bi in b]) else "./" return sign + multjoin(a, a_str) + divsym + "(%s)" % multjoin(b, b_str)
def collect(expr, syms, func=None, evaluate=None, exact=False, distribute_order_term=True): """ Collect additive terms of an expression. Explanation =========== This function collects additive terms of an expression with respect to a list of expression up to powers with rational exponents. By the term symbol here are meant arbitrary expressions, which can contain powers, products, sums etc. In other words symbol is a pattern which will be searched for in the expression's terms. The input expression is not expanded by :func:`collect`, so user is expected to provide an expression is an appropriate form. This makes :func:`collect` more predictable as there is no magic happening behind the scenes. However, it is important to note, that powers of products are converted to products of powers using the :func:`~.expand_power_base` function. There are two possible types of output. First, if ``evaluate`` flag is set, this function will return an expression with collected terms or else it will return a dictionary with expressions up to rational powers as keys and collected coefficients as values. Examples ======== >>> from sympy import S, collect, expand, factor, Wild >>> from sympy.abc import a, b, c, x, y This function can collect symbolic coefficients in polynomials or rational expressions. It will manage to find all integer or rational powers of collection variable:: >>> collect(a*x**2 + b*x**2 + a*x - b*x + c, x) c + x**2*(a + b) + x*(a - b) The same result can be achieved in dictionary form:: >>> d = collect(a*x**2 + b*x**2 + a*x - b*x + c, x, evaluate=False) >>> d[x**2] a + b >>> d[x] a - b >>> d[S.One] c You can also work with multivariate polynomials. However, remember that this function is greedy so it will care only about a single symbol at time, in specification order:: >>> collect(x**2 + y*x**2 + x*y + y + a*y, [x, y]) x**2*(y + 1) + x*y + y*(a + 1) Also more complicated expressions can be used as patterns:: >>> from sympy import sin, log >>> collect(a*sin(2*x) + b*sin(2*x), sin(2*x)) (a + b)*sin(2*x) >>> collect(a*x*log(x) + b*(x*log(x)), x*log(x)) x*(a + b)*log(x) You can use wildcards in the pattern:: >>> w = Wild('w1') >>> collect(a*x**y - b*x**y, w**y) x**y*(a - b) It is also possible to work with symbolic powers, although it has more complicated behavior, because in this case power's base and symbolic part of the exponent are treated as a single symbol:: >>> collect(a*x**c + b*x**c, x) a*x**c + b*x**c >>> collect(a*x**c + b*x**c, x**c) x**c*(a + b) However if you incorporate rationals to the exponents, then you will get well known behavior:: >>> collect(a*x**(2*c) + b*x**(2*c), x**c) x**(2*c)*(a + b) Note also that all previously stated facts about :func:`collect` function apply to the exponential function, so you can get:: >>> from sympy import exp >>> collect(a*exp(2*x) + b*exp(2*x), exp(x)) (a + b)*exp(2*x) If you are interested only in collecting specific powers of some symbols then set ``exact`` flag in arguments:: >>> collect(a*x**7 + b*x**7, x, exact=True) a*x**7 + b*x**7 >>> collect(a*x**7 + b*x**7, x**7, exact=True) x**7*(a + b) You can also apply this function to differential equations, where derivatives of arbitrary order can be collected. Note that if you collect with respect to a function or a derivative of a function, all derivatives of that function will also be collected. Use ``exact=True`` to prevent this from happening:: >>> from sympy import Derivative as D, collect, Function >>> f = Function('f') (x) >>> collect(a*D(f,x) + b*D(f,x), D(f,x)) (a + b)*Derivative(f(x), x) >>> collect(a*D(D(f,x),x) + b*D(D(f,x),x), f) (a + b)*Derivative(f(x), (x, 2)) >>> collect(a*D(D(f,x),x) + b*D(D(f,x),x), D(f,x), exact=True) a*Derivative(f(x), (x, 2)) + b*Derivative(f(x), (x, 2)) >>> collect(a*D(f,x) + b*D(f,x) + a*f + b*f, f) (a + b)*f(x) + (a + b)*Derivative(f(x), x) Or you can even match both derivative order and exponent at the same time:: >>> collect(a*D(D(f,x),x)**2 + b*D(D(f,x),x)**2, D(f,x)) (a + b)*Derivative(f(x), (x, 2))**2 Finally, you can apply a function to each of the collected coefficients. For example you can factorize symbolic coefficients of polynomial:: >>> f = expand((x + a + 1)**3) >>> collect(f, x, factor) x**3 + 3*x**2*(a + 1) + 3*x*(a + 1)**2 + (a + 1)**3 .. note:: Arguments are expected to be in expanded form, so you might have to call :func:`~.expand` prior to calling this function. See Also ======== collect_const, collect_sqrt, rcollect """ from sympy.core.assumptions import assumptions from sympy.utilities.iterables import sift from sympy.core.symbol import Dummy, Wild expr = sympify(expr) syms = [sympify(i) for i in (syms if iterable(syms) else [syms])] # replace syms[i] if it is not x, -x or has Wild symbols cond = lambda x: x.is_Symbol or (-x).is_Symbol or bool( x.atoms(Wild)) _, nonsyms = sift(syms, cond, binary=True) if nonsyms: reps = dict(zip(nonsyms, [Dummy(**assumptions(i)) for i in nonsyms])) syms = [reps.get(s, s) for s in syms] rv = collect(expr.subs(reps), syms, func=func, evaluate=evaluate, exact=exact, distribute_order_term=distribute_order_term) urep = {v: k for k, v in reps.items()} if not isinstance(rv, dict): return rv.xreplace(urep) else: return {urep.get(k, k).xreplace(urep): v.xreplace(urep) for k, v in rv.items()} if evaluate is None: evaluate = global_parameters.evaluate def make_expression(terms): product = [] for term, rat, sym, deriv in terms: if deriv is not None: var, order = deriv while order > 0: term, order = Derivative(term, var), order - 1 if sym is None: if rat is S.One: product.append(term) else: product.append(Pow(term, rat)) else: product.append(Pow(term, rat*sym)) return Mul(*product) def parse_derivative(deriv): # scan derivatives tower in the input expression and return # underlying function and maximal differentiation order expr, sym, order = deriv.expr, deriv.variables[0], 1 for s in deriv.variables[1:]: if s == sym: order += 1 else: raise NotImplementedError( 'Improve MV Derivative support in collect') while isinstance(expr, Derivative): s0 = expr.variables[0] for s in expr.variables: if s != s0: raise NotImplementedError( 'Improve MV Derivative support in collect') if s0 == sym: expr, order = expr.expr, order + len(expr.variables) else: break return expr, (sym, Rational(order)) def parse_term(expr): """Parses expression expr and outputs tuple (sexpr, rat_expo, sym_expo, deriv) where: - sexpr is the base expression - rat_expo is the rational exponent that sexpr is raised to - sym_expo is the symbolic exponent that sexpr is raised to - deriv contains the derivatives the the expression For example, the output of x would be (x, 1, None, None) the output of 2**x would be (2, 1, x, None). """ rat_expo, sym_expo = S.One, None sexpr, deriv = expr, None if expr.is_Pow: if isinstance(expr.base, Derivative): sexpr, deriv = parse_derivative(expr.base) else: sexpr = expr.base if expr.exp.is_Number: rat_expo = expr.exp else: coeff, tail = expr.exp.as_coeff_Mul() if coeff.is_Number: rat_expo, sym_expo = coeff, tail else: sym_expo = expr.exp elif isinstance(expr, exp): arg = expr.args[0] if arg.is_Rational: sexpr, rat_expo = S.Exp1, arg elif arg.is_Mul: coeff, tail = arg.as_coeff_Mul(rational=True) sexpr, rat_expo = exp(tail), coeff elif isinstance(expr, Derivative): sexpr, deriv = parse_derivative(expr) return sexpr, rat_expo, sym_expo, deriv def parse_expression(terms, pattern): """Parse terms searching for a pattern. Terms is a list of tuples as returned by parse_terms; Pattern is an expression treated as a product of factors. """ pattern = Mul.make_args(pattern) if len(terms) < len(pattern): # pattern is longer than matched product # so no chance for positive parsing result return None else: pattern = [parse_term(elem) for elem in pattern] terms = terms[:] # need a copy elems, common_expo, has_deriv = [], None, False for elem, e_rat, e_sym, e_ord in pattern: if elem.is_Number and e_rat == 1 and e_sym is None: # a constant is a match for everything continue for j in range(len(terms)): if terms[j] is None: continue term, t_rat, t_sym, t_ord = terms[j] # keeping track of whether one of the terms had # a derivative or not as this will require rebuilding # the expression later if t_ord is not None: has_deriv = True if (term.match(elem) is not None and (t_sym == e_sym or t_sym is not None and e_sym is not None and t_sym.match(e_sym) is not None)): if exact is False: # we don't have to be exact so find common exponent # for both expression's term and pattern's element expo = t_rat / e_rat if common_expo is None: # first time common_expo = expo else: # common exponent was negotiated before so # there is no chance for a pattern match unless # common and current exponents are equal if common_expo != expo: common_expo = 1 else: # we ought to be exact so all fields of # interest must match in every details if e_rat != t_rat or e_ord != t_ord: continue # found common term so remove it from the expression # and try to match next element in the pattern elems.append(terms[j]) terms[j] = None break else: # pattern element not found return None return [_f for _f in terms if _f], elems, common_expo, has_deriv if evaluate: if expr.is_Add: o = expr.getO() or 0 expr = expr.func(*[ collect(a, syms, func, True, exact, distribute_order_term) for a in expr.args if a != o]) + o elif expr.is_Mul: return expr.func(*[ collect(term, syms, func, True, exact, distribute_order_term) for term in expr.args]) elif expr.is_Pow: b = collect( expr.base, syms, func, True, exact, distribute_order_term) return Pow(b, expr.exp) syms = [expand_power_base(i, deep=False) for i in syms] order_term = None if distribute_order_term: order_term = expr.getO() if order_term is not None: if order_term.has(*syms): order_term = None else: expr = expr.removeO() summa = [expand_power_base(i, deep=False) for i in Add.make_args(expr)] collected, disliked = defaultdict(list), S.Zero for product in summa: c, nc = product.args_cnc(split_1=False) args = list(ordered(c)) + nc terms = [parse_term(i) for i in args] small_first = True for symbol in syms: if SYMPY_DEBUG: print("DEBUG: parsing of expression %s with symbol %s " % ( str(terms), str(symbol)) ) if isinstance(symbol, Derivative) and small_first: terms = list(reversed(terms)) small_first = not small_first result = parse_expression(terms, symbol) if SYMPY_DEBUG: print("DEBUG: returned %s" % str(result)) if result is not None: if not symbol.is_commutative: raise AttributeError("Can not collect noncommutative symbol") terms, elems, common_expo, has_deriv = result # when there was derivative in current pattern we # will need to rebuild its expression from scratch if not has_deriv: margs = [] for elem in elems: if elem[2] is None: e = elem[1] else: e = elem[1]*elem[2] margs.append(Pow(elem[0], e)) index = Mul(*margs) else: index = make_expression(elems) terms = expand_power_base(make_expression(terms), deep=False) index = expand_power_base(index, deep=False) collected[index].append(terms) break else: # none of the patterns matched disliked += product # add terms now for each key collected = {k: Add(*v) for k, v in collected.items()} if disliked is not S.Zero: collected[S.One] = disliked if order_term is not None: for key, val in collected.items(): collected[key] = val + order_term if func is not None: collected = { key: func(val) for key, val in collected.items()} if evaluate: return Add(*[key*val for key, val in collected.items()]) else: return collected