def eval(cls, x, k): x = sympify(x) k = sympify(k) if x is S.NaN: return S.NaN elif k.is_Integer: if k is S.NaN: return S.NaN elif k is S.Zero: return S.One else: if k.is_positive: if x is S.Infinity: return S.Infinity elif x is S.NegativeInfinity: if k.is_odd: return S.NegativeInfinity else: return S.Infinity else: return reduce(lambda r, i: r*(x - i), xrange(0, int(k)), 1) else: if x is S.Infinity: return S.Infinity elif x is S.NegativeInfinity: return S.Infinity else: return 1/reduce(lambda r, i: r*(x + i), xrange(1, abs(int(k)) + 1), 1)
def dim_simplify(expr): """ Simplify expression by recursively evaluating the dimension arguments. This function proceeds to a very rough dimensional analysis. It tries to simplify expression with dimensions, and it deletes all what multiplies a dimension without being a dimension. This is necessary to avoid strange behavior when Add(L, L) be transformed into Mul(2, L). """ args = [] for arg in expr.args: if isinstance(arg, (Mul, Pow, Add)): arg = dim_simplify(arg) args.append(arg) if isinstance(expr, Pow): return args[0].pow(args[1]) elif isinstance(expr, Add): args = [arg for arg in args if isinstance(arg, Dimension)] return reduce(lambda x, y: x.add(y), args) elif isinstance(expr, Mul): args = [arg for arg in args if isinstance(arg, Dimension)] return reduce(lambda x, y: x.mul(y), args) else: return expr
def _rebuild(expr): generator = mapping.get(expr) if generator is not None: return generator elif expr.is_Add: return reduce(add, list(map(_rebuild, expr.args))) elif expr.is_Mul: return reduce(mul, list(map(_rebuild, expr.args))) elif expr.is_Pow or isinstance(expr, (ExpBase, Exp1)): b, e = expr.as_base_exp() # look for bg**eg whose integer power may be b**e for gen, (bg, eg) in powers: if bg == b and Mod(e, eg) == 0: return mapping.get(gen)**int(e/eg) if e.is_Integer and e is not S.One: return _rebuild(b)**int(e) try: return domain.convert(expr) except CoercionFailed: if not domain.is_Field and domain.has_assoc_Field: return domain.get_field().convert(expr) else: raise
def dynamicsymbols(names, level=0): """Uses symbols and Function for functions of time. Creates a SymPy UndefinedFunction, which is then initialized as a function of a variable, the default being Symbol('t'). Parameters ========== names : str Names of the dynamic symbols you want to create; works the same way as inputs to symbols level : int Level of differentiation of the returned function; d/dt once of t, twice of t, etc. Examples ======== >>> from sympy.physics.vector import dynamicsymbols >>> from sympy import diff, Symbol >>> q1 = dynamicsymbols('q1') >>> q1 q1(t) >>> diff(q1, Symbol('t')) Derivative(q1(t), t) """ esses = symbols(names, cls=Function) t = dynamicsymbols._t if iterable(esses): esses = [reduce(diff, [t] * level, e(t)) for e in esses] return esses else: return reduce(diff, [t] * level, esses(t))
def qsimplify(cls, expr): """ Simplify expression by recursively evaluating the quantity arguments. If units are encountered, as it can be when using Constant, they are converted to quantity. """ def redmul(x, y): """ Function used to combine args in multiplications. This is necessary because the previous computation was not commutative, and Mul(3, u) was not simplified; but Mul(u, 3) was. """ if isinstance(x, Quantity): return x.mul(y) elif isinstance(y, Quantity): return y.mul(x) else: return x*y args = [] for arg in expr.args: arg = arg.evalf() if isinstance(arg, (Mul, Pow, Add)): arg = cls.qsimplify(arg) args.append(arg) q_args, o_args = [], [] for arg in args: if isinstance(arg, Quantity): q_args.append(arg) elif isinstance(arg, Unit): # replace unit by a quantity to make the simplification q_args.append(arg.as_quantity) else: o_args.append(arg) if isinstance(expr, Pow): return args[0].pow(args[1]) elif isinstance(expr, Add): if q_args != []: quantities = reduce(lambda x, y: x.add(y), q_args) else: quantities = [] return reduce(lambda x, y: x+y, o_args, quantities) elif isinstance(expr, Mul): if q_args != []: quantities = reduce(lambda x, y: x.mul(y), q_args) else: quantities = [] return reduce(redmul, o_args, quantities) else: return expr
def eval(cls, x, k): x = sympify(x) k = sympify(k) if x is S.NaN or k is S.NaN: return S.NaN elif x is S.One: return factorial(k) elif k.is_Integer: if k is S.Zero: return S.One else: if k.is_positive: if x is S.Infinity: return S.Infinity elif x is S.NegativeInfinity: if k.is_odd: return S.NegativeInfinity else: return S.Infinity else: if isinstance(x, Poly): gens = x.gens if len(gens)!= 1: raise ValueError("rf only defined for " "polynomials on one generator") else: return reduce(lambda r, i: r*(x.shift(i).expand()), range(0, int(k)), 1) else: return reduce(lambda r, i: r*(x + i), range(0, int(k)), 1) else: if x is S.Infinity: return S.Infinity elif x is S.NegativeInfinity: return S.Infinity else: if isinstance(x, Poly): gens = x.gens if len(gens)!= 1: raise ValueError("rf only defined for " "polynomials on one generator") else: return 1/reduce(lambda r, i: r*(x.shift(-i).expand()), range(1, abs(int(k)) + 1), 1) else: return 1/reduce(lambda r, i: r*(x - i), range(1, abs(int(k)) + 1), 1)
def merge_explicit(matadd): """ Merge explicit MatrixBase arguments Examples ======== >>> from sympy import MatrixSymbol, eye, Matrix, MatAdd, pprint >>> from sympy.matrices.expressions.matadd import merge_explicit >>> A = MatrixSymbol('A', 2, 2) >>> B = eye(2) >>> C = Matrix([[1, 2], [3, 4]]) >>> X = MatAdd(A, B, C) >>> pprint(X) [1 0] [1 2] A + [ ] + [ ] [0 1] [3 4] >>> pprint(merge_explicit(X)) [2 2] A + [ ] [3 5] """ groups = sift(matadd.args, lambda arg: isinstance(arg, MatrixBase)) if len(groups[True]) > 1: return MatAdd(*(groups[False] + [reduce(add, groups[True])])) else: return matadd
def _get_indices_Mul(expr, return_dummies=False): """Determine the outer indices of a Mul object. >>> from sympy.tensor.index_methods import _get_indices_Mul >>> from sympy.tensor.indexed import IndexedBase, Idx >>> i, j, k = map(Idx, ['i', 'j', 'k']) >>> x = IndexedBase('x') >>> y = IndexedBase('y') >>> _get_indices_Mul(x[i, k]*y[j, k]) (set([i, j]), {}) >>> _get_indices_Mul(x[i, k]*y[j, k], return_dummies=True) (set([i, j]), {}, (k,)) """ inds = list(map(get_indices, expr.args)) inds, syms = list(zip(*inds)) inds = list(map(list, inds)) inds = list(reduce(lambda x, y: x + y, inds)) inds, dummies = _remove_repeated(inds) symmetry = {} for s in syms: for pair in s: if pair in symmetry: symmetry[pair] *= s[pair] else: symmetry[pair] = s[pair] if return_dummies: return inds, symmetry, dummies else: return inds, symmetry
def dim_simplify(expr): """ NOTE: this function could be deprecated in the future. Simplify expression by recursively evaluating the dimension arguments. This function proceeds to a very rough dimensional analysis. It tries to simplify expression with dimensions, and it deletes all what multiplies a dimension without being a dimension. This is necessary to avoid strange behavior when Add(L, L) be transformed into Mul(2, L). """ if isinstance(expr, Dimension): return expr if isinstance(expr, Pow): return dim_simplify(expr.base)**dim_simplify(expr.exp) elif isinstance(expr, Function): return dim_simplify(expr.args[0]) elif isinstance(expr, Add): if (all(isinstance(arg, Dimension) for arg in expr.args) or all(arg.is_dimensionless for arg in expr.args if isinstance(arg, Dimension))): return reduce(lambda x, y: x.add(y), expr.args) else: raise ValueError("Dimensions cannot be added: %s" % expr) elif isinstance(expr, Mul): return Dimension(Mul(*[dim_simplify(i).name for i in expr.args if isinstance(i, Dimension)])) raise ValueError("Cannot be simplifed: %s", expr)
def prde_normal_denom(fa, fd, G, DE): """ Parametric Risch Differential Equation - Normal part of the denominator. Given a derivation D on k[t] and f, g1, ..., gm in k(t) with f weakly normalized with respect to t, return the tuple (a, b, G, h) such that a, h in k[t], b in k<t>, G = [g1, ..., gm] in k(t)^m, and for any solution c1, ..., cm in Const(k) and y in k(t) of Dy + f*y == Sum(ci*gi, (i, 1, m)), q == y*h in k<t> satisfies a*Dq + b*q == Sum(ci*Gi, (i, 1, m)). """ dn, ds = splitfactor(fd, DE) Gas, Gds = list(zip(*G)) gd = reduce(lambda i, j: i.lcm(j), Gds, Poly(1, DE.t)) en, es = splitfactor(gd, DE) p = dn.gcd(en) h = en.gcd(en.diff(DE.t)).quo(p.gcd(p.diff(DE.t))) a = dn*h c = a*h ba = a*fa - dn*derivation(h, DE)*fd ba, bd = ba.cancel(fd, include=True) G = [(c*A).cancel(D, include=True) for A, D in G] return (a, (ba, bd), G, h)
def pull_out_u_rl(integrand): if any([integrand.has(f) for f in functions]): args = [arg for arg in integrand.args if any(isinstance(arg, cls) for cls in functions)] if args: u = reduce(lambda a, b: a * b, args) dv = integrand / u return u, dv
def _eval_matrix_mul(self, other): from sympy import Add # cache attributes for faster access self_rows, self_cols = self.rows, self.cols other_rows, other_cols = other.rows, other.cols other_len = other_rows * other_cols new_mat_rows = self.rows new_mat_cols = other.cols # preallocate the array new_mat = [S.Zero]*new_mat_rows*new_mat_cols # if we multiply an n x 0 with a 0 x m, the # expected behavior is to produce an n x m matrix of zeros if self.cols != 0 and other.rows != 0: # cache self._mat and other._mat for performance mat = self._mat other_mat = other._mat for i in range(len(new_mat)): row, col = i // new_mat_cols, i % new_mat_cols row_indices = range(self_cols*row, self_cols*(row+1)) col_indices = range(col, other_len, other_cols) vec = (mat[a]*other_mat[b] for a,b in zip(row_indices, col_indices)) try: new_mat[i] = Add(*vec) except (TypeError, SympifyError): # Block matrices don't work with `sum` or `Add` (ISSUE #11599) # They don't work with `sum` because `sum` tries to add `0` # initially, and for a matrix, that is a mix of a scalar and # a matrix, which raises a TypeError. Fall back to a # block-matrix-safe way to multiply if the `sum` fails. vec = (mat[a]*other_mat[b] for a,b in zip(row_indices, col_indices)) new_mat[i] = reduce(lambda a,b: a + b, vec) return classof(self, other)._new(new_mat_rows, new_mat_cols, new_mat, copy=False)
def prde_linear_constraints(a, b, G, DE): """ Parametric Risch Differential Equation - Generate linear constraints on the constants. Given a derivation D on k[t], a, b, in k[t] with gcd(a, b) == 1, and G = [g1, ..., gm] in k(t)^m, return Q = [q1, ..., qm] in k[t]^m and a matrix M with entries in k(t) such that for any solution c1, ..., cm in Const(k) and p in k[t] of a*Dp + b*p == Sum(ci*gi, (i, 1, m)), (c1, ..., cm) is a solution of Mx == 0, and p and the ci satisfy a*Dp + b*p == Sum(ci*qi, (i, 1, m)). Because M has entries in k(t), and because Matrix doesn't play well with Poly, M will be a Matrix of Basic expressions. """ m = len(G) Gns, Gds = list(zip(*G)) d = reduce(lambda i, j: i.lcm(j), Gds) d = Poly(d, field=True) Q = [(ga*(d).quo(gd)).div(d) for ga, gd in G] if not all([ri.is_zero for _, ri in Q]): N = max([ri.degree(DE.t) for _, ri in Q]) M = Matrix(N + 1, m, lambda i, j: Q[j][1].nth(i)) else: M = Matrix() # No constraints, return the empty matrix. qs, _ = list(zip(*Q)) return (qs, M)
def _module_quotient(self, other, relations=False): # See: [SCA, section 2.8.4] if relations and len(other.gens) != 1: raise NotImplementedError if len(other.gens) == 0: return self.ring.ideal(1) elif len(other.gens) == 1: # We do some trickery. Let f be the (vector!) generating ``other`` # and f1, .., fn be the (vectors) generating self. # Consider the submodule of R^{r+1} generated by (f, 1) and # {(fi, 0) | i}. Then the intersection with the last module # component yields the quotient. g1 = list(other.gens[0]) + [1] gi = [list(x) + [0] for x in self.gens] # NOTE: We *need* to use an elimination order M = self.ring.free_module(self.rank + 1).submodule(*([g1] + gi), order='ilex', TOP=False) if not relations: return self.ring.ideal(*[x[-1] for x in M._groebner_vec() if all(y == self.ring.zero for y in x[:-1])]) else: G, R = M._groebner_vec(extended=True) indices = [i for i, x in enumerate(G) if all(y == self.ring.zero for y in x[:-1])] return (self.ring.ideal(*[G[i][-1] for i in indices]), [[-x for x in R[i][1:]] for i in indices]) # For more generators, we use I : <h1, .., hn> = intersection of # {I : <hi> | i} # TODO this can be done more efficiently return reduce(lambda x, y: x.intersect(y), (self._module_quotient(self.container.submodule(x)) for x in other.gens))
def test_treeapply(): tree = ([3, 3], [4, 1], 2) assert treeapply(tree, {list: min, tuple: max}) == 3 add = lambda *args: sum(args) mul = lambda *args: reduce(lambda a, b: a*b, args, 1) assert treeapply(tree, {list: add, tuple: mul}) == 60
def get_total_scale_factor(expr): if isinstance(expr, Mul): return reduce(lambda x, y: x * y, [get_total_scale_factor(i) for i in expr.args]) elif isinstance(expr, Pow): return get_total_scale_factor(expr.base) ** expr.exp elif isinstance(expr, Quantity): return expr.scale_factor return expr
def _separate(eq, dep, others): """Separate expression into two parts based on dependencies of variables.""" # FIRST PASS # Extract derivatives depending our separable variable... terms = set() for term in eq.args: if term.is_Mul: for i in term.args: if i.is_Derivative and not i.has(*others): terms.add(term) continue elif term.is_Derivative and not term.has(*others): terms.add(term) # Find the factor that we need to divide by div = set() for term in terms: ext, sep = term.expand().as_independent(dep) # Failed? if sep.has(*others): return None div.add(ext) # FIXME: Find lcm() of all the divisors and divide with it, instead of # current hack :( # http://code.google.com/p/sympy/issues/detail?id=1498 if len(div) > 0: final = 0 for term in eq.args: eqn = 0 for i in div: eqn += term / i final += simplify(eqn) eq = final # SECOND PASS - separate the derivatives div = set() lhs = rhs = 0 for term in eq.args: # Check, whether we have already term with independent variable... if not term.has(*others): lhs += term continue # ...otherwise, try to separate temp, sep = term.expand().as_independent(dep) # Failed? if sep.has(*others): return None # Extract the divisors div.add(sep) rhs -= term.expand() # Do the division fulldiv = reduce(operator.add, div) lhs = simplify(lhs/fulldiv).expand() rhs = simplify(rhs/fulldiv).expand() # ...and check whether we were successful :) if lhs.has(*others) or rhs.has(dep): return None return [lhs, rhs]
def limited_integrate_reduce(fa, fd, G, DE): """ Simpler version of step 1 & 2 for the limited integration problem. Given a derivation D on k(t) and f, g1, ..., gn in k(t), return (a, b, h, N, g, V) such that a, b, h in k[t], N is a non-negative integer, g in k(t), V == [v1, ..., vm] in k(t)^m, and for any solution v in k(t), c1, ..., cm in C of f == Dv + Sum(ci*wi, (i, 1, m)), p = v*h is in k<t>, and p and the ci satisfy a*Dp + b*p == g + Sum(ci*vi, (i, 1, m)). Furthermore, if S1irr == Sirr, then p is in k[t], and if t is nonlinear or Liouvillian over k, then deg(p) <= N. So that the special part is always computed, this function calls the more general prde_special_denom() automatically if it cannot determine that S1irr == Sirr. Furthermore, it will automatically call bound_degree() when t is linear and non-Liouvillian, which for the transcendental case, implies that Dt == a*t + b with for some a, b in k*. """ dn, ds = splitfactor(fd, DE) E = [splitfactor(gd, DE) for _, gd in G] En, Es = list(zip(*E)) c = reduce(lambda i, j: i.lcm(j), (dn,) + En) # lcm(dn, en1, ..., enm) hn = c.gcd(c.diff(DE.t)) a = hn b = -derivation(hn, DE) N = 0 # These are the cases where we know that S1irr = Sirr, but there could be # others, and this algorithm will need to be extended to handle them. if DE.case in ['base', 'primitive', 'exp', 'tan']: hs = reduce(lambda i, j: i.lcm(j), (ds,) + Es) # lcm(ds, es1, ..., esm) a = hn*hs b = -derivation(hn, DE) - (hn*derivation(hs, DE)).quo(hs) mu = min(order_at_oo(fa, fd, DE.t), min([order_at_oo(ga, gd, DE.t) for ga, gd in G])) # So far, all the above are also nonlinear or Liouvillian, but if this # changes, then this will need to be updated to call bound_degree() # as per the docstring of this function (DE.case == 'other_linear'). N = hn.degree(DE.t) + hs.degree(DE.t) + max(0, 1 - DE.d.degree(DE.t) - mu) else: # TODO: implement this raise NotImplementedError V = [(-a*hn*ga).cancel(gd, include=True) for ga, gd in G] return (a, b, a, N, (a*hn*fa).cancel(fd, include=True), V)
def test_PolyRing_mul(): R, x = ring("x", ZZ) F = [ x**2 + 2*i + 3 for i in range(4) ] assert R.mul(F) == reduce(mul, F) == x**8 + 24*x**6 + 206*x**4 + 744*x**2 + 945 R, = ring("", ZZ) assert R.mul([2, 3, 5]) == 30
def compute(l): # first check that no two differ by an integer for i, b in enumerate(l): if not b.is_Rational: return oo for j in range(i + 1, len(l)): if not Mod((b - l[j]).simplify(), 1): return oo return reduce(ilcm, (x.q for x in l), 1)
def test_PolyRing_add(): R, x = ring("x", ZZ) F = [ x**2 + 2*i + 3 for i in range(4) ] assert R.add(F) == reduce(add, F) == 4*x**2 + 24 R, = ring("", ZZ) assert R.add([2, 5, 7]) == 14
def perp_to_subspace(vec, basis): """projects vec onto the subspace given by the orthogonal basis ``basis``""" components = [project(vec, b) for b in basis] if len(basis) == 0: return vec return vec - reduce(lambda a, b: a + b, components)
def _rebuild(expr): generator = mapping.get(expr) if generator is not None: return generator elif expr.is_Add: return reduce(add, list(map(_rebuild, expr.args))) elif expr.is_Mul: return reduce(mul, list(map(_rebuild, expr.args))) elif expr.is_Pow and expr.exp.is_Integer: return _rebuild(expr.base)**int(expr.exp) else: try: return domain.convert(expr) except CoercionFailed: if not domain.has_Field and domain.has_assoc_Field: return domain.get_field().convert(expr) else: raise
def pull_out_u_rl(integrand): if any([integrand.has(f) for f in functions]): args = [ arg for arg in integrand.args if any( isinstance(arg, cls) for cls in functions) ] if args: u = reduce(lambda a, b: a * b, args) dv = integrand / u return u, dv
def str_combinations(base, lst, rank=1, mode='_'): """ Construct a list of strings of the form 'base+mode+indexes' where the indexes are formed by converting 'lst' to a list of strings and then forming the 'indexes' by concatenating combinations of elements from 'lst' taken 'rank' at a time. """ str_lst = list(map(lambda x: base + mode + x, map(lambda x: reduce(operator.add, x), combinations(map(lambda x: str(x), lst), rank)))) return str_lst
def list_can_dims(self): """ List all canonical dimension names. """ if self._list_can_dims is None: gen = reduce(lambda x, y: x.mul(y), self._base_dims) self._list_can_dims = tuple(sorted(map(str, gen.keys()))) return self._list_can_dims
def reduce_inequalities(inequalities, symbols=[]): """Reduce a system of inequalities with rational coefficients. Examples ======== >>> from sympy import sympify as S, Symbol >>> from sympy.abc import x, y >>> from sympy.solvers.inequalities import reduce_inequalities >>> reduce_inequalities(S(0) <= x + 3, []) And(-3 <= x, x < oo) >>> reduce_inequalities(S(0) <= x + y*2 - 1, [x]) -2*y + 1 <= x """ if not iterable(inequalities): inequalities = [inequalities] # prefilter keep = [] for i in inequalities: if isinstance(i, Relational): i = i.func(i.lhs.as_expr() - i.rhs.as_expr(), 0) elif i not in (True, False): i = Eq(i, 0) if i == True: continue elif i == False: return S.false if i.lhs.is_number: raise NotImplementedError( "could not determine truth value of %s" % i) keep.append(i) inequalities = keep del keep gens = reduce(set.union, [i.free_symbols for i in inequalities], set()) if not iterable(symbols): symbols = [symbols] symbols = set(symbols) or gens # make vanilla symbol real recast = dict([(i, Dummy(i.name, real=True)) for i in gens if i.is_real is None]) inequalities = [i.xreplace(recast) for i in inequalities] symbols = set([i.xreplace(recast) for i in symbols]) # solve system rv = _reduce_inequalities(inequalities, symbols) # restore original symbols and return return rv.xreplace(dict([(v, k) for k, v in recast.items()]))
def str_combinations(base, lst, rank=1, mode='_'): """ Construct a list of strings of the form 'base+mode+indexes' where the indexes are formed by converting 'lst' to a list of strings and then forming the 'indexes' by concatenating combinations of elements from 'lst' taken 'rank' at a time. """ a1 = combinations([str(x) for x in lst], rank) a2 = [reduce(operator.add, x) for x in a1] str_lst = [base + mode + x for x in a2] return str_lst
def can_transf_matrix(self): """ Return the canonical transformation matrix from the canonical to the base dimension basis. It is the inverse of the matrix computed with inv_can_transf_matrix(). """ #TODO: the inversion will fail if the system is inconsistent, for # example if the matrix is not a square return reduce(lambda x, y: x.row_join(y), [self.dim_can_vector(d) for d in self._base_dims]).inv()
def dim_simplify(expr): """ Simplify expression by recursively evaluating the dimension arguments. This function proceeds to a very rough dimensional analysis. It tries to simplify expression with dimensions, and it deletes all what multiplies a dimension without being a dimension. This is necessary to avoid strange behavior when Add(L, L) be transformed into Mul(2, L). """ args = [] for arg in expr.args: if isinstance(arg, (Mul, Pow, Add)): arg = dim_simplify(arg) args.append(arg) if all([ arg.is_number or (isinstance(arg, Dimension) and arg.is_dimensionless) for arg in args ]): return Dimension({}) if isinstance(expr, Pow): if isinstance(args[0], Dimension): return args[0].pow(args[1]) else: raise ValueError("Basis of Pow is not a Dimension: %s" % args[0]) elif isinstance(expr, Add): if (all(isinstance(arg, Dimension) for arg in args) or all(arg.is_dimensionless() for arg in args if isinstance(arg, Dimension))): return reduce(lambda x, y: x.add(y), args) else: raise ValueError("Dimensions cannot be added: %s" % expr) elif isinstance(expr, Mul): args = [arg for arg in args if isinstance(arg, Dimension)] return reduce(lambda x, y: x.mul(y), args) raise ValueError("Cannot be simplifed: %s", expr)
def dim_simplify(expr): """ Simplify expression by recursively evaluating the dimension arguments. This function proceeds to a very rough dimensional analysis. It tries to simplify expression with dimensions, and it deletes all what multiplies a dimension without being a dimension. This is necessary to avoid strange behavior when Add(L, L) be transformed into Mul(2, L). """ if isinstance(expr, Dimension): return expr if isinstance(expr, Symbol): return None args = [] for arg in expr.args: if isinstance(arg, (Mul, Pow, Add, Symbol, Function)): arg = dim_simplify(arg) args.append(arg) if all([arg!=None and (arg.is_number or (isinstance(arg, Dimension) and arg.is_dimensionless)) for arg in args]): return Dimension({}) if isinstance(expr, Function): raise ValueError("Arguments of this function cannot have a dimension: %s" % expr) elif isinstance(expr, Pow): if isinstance(args[0], Dimension): return args[0].pow(args[1]) else: return None elif isinstance(expr, Add): dimargs = [arg for arg in args if isinstance(arg, Dimension)] if (all(isinstance(arg, Dimension) or arg==None for arg in args) or all(arg.is_dimensionless for arg in dimargs)): if len(dimargs)>0: return reduce(lambda x, y: x.add(y), dimargs) else: return Dimension({}) else: raise ValueError("Dimensions cannot be added: %s" % expr) elif isinstance(expr, Mul): result = Dimension({}) for arg in args: if isinstance(arg, Dimension): result = result.mul(arg) elif arg == None: return None return result return None
def can_transf_matrix(self): """ Return the canonical transformation matrix from the canonical to the base dimension basis. It is the inverse of the matrix computed with inv_can_transf_matrix(). """ #TODO: the inversion will fail if the system is inconsistent, for # example if the matrix is not a square return reduce(lambda x, y: x.row_join(y), [self.dim_can_vector(d) for d in self._base_dims] ).inv()
def _eval_derivative(self, s): # adapted from Mul._eval_derivative args = list(self.args) terms = [] for i in range(len(args)): d = args[i].diff(s) if d: # Note: reduce is used in step of Mul as Mul is unable to # handle subtypes and operation priority: terms.append( reduce(lambda x, y: x * y, (args[:i] + [d] + args[i + 1:]), S.One)) return VecAdd.fromiter(terms)
def str_combinations(base, lst, rank=1, mode='_'): """ Construct a list of strings of the form 'base+mode+indexes' where the indexes are formed by converting 'lst' to a list of strings and then forming the 'indexes' by concatenating combinations of elements from 'lst' taken 'rank' at a time. """ str_lst = list( map( lambda x: base + mode + x, map(lambda x: reduce(operator.add, x), combinations(map(lambda x: str(x), lst), rank)))) return str_lst
def dim_simplify(expr): """ Simplify expression by recursively evaluating the dimension arguments. This function proceeds to a very rough dimensional analysis. It tries to simplify expression with dimensions, and it deletes all what multiplies a dimension without being a dimension. This is necessary to avoid strange behavior when Add(L, L) be transformed into Mul(2, L). """ if isinstance(expr, Dimension): return expr args = [] for arg in expr.args: if isinstance(arg, (Mul, Pow, Add)): arg = dim_simplify(arg) args.append(arg) if all([arg.is_number or (isinstance(arg, Dimension) and arg.is_dimensionless) for arg in args]): return Dimension({}) if isinstance(expr, Pow): if isinstance(args[0], Dimension): return args[0].pow(args[1]) else: raise ValueError("Basis of Pow is not a Dimension: %s" % args[0]) elif isinstance(expr, Add): if (all(isinstance(arg, Dimension) for arg in args) or all(arg.is_dimensionless for arg in args if isinstance(arg, Dimension))): return reduce(lambda x, y: x.add(y), args) else: raise ValueError("Dimensions cannot be added: %s" % expr) elif isinstance(expr, Mul): args = [arg for arg in args if isinstance(arg, Dimension)] return reduce(lambda x, y: x.mul(y), args) raise ValueError("Cannot be simplifed: %s", expr)
def free_symbols(self): """Return from the atoms of self those which are free symbols. For most expressions, all symbols are free symbols. For some classes this is not true. e.g. Integrals use Symbols for the dummy variables which are bound variables, so Integral has a method to return all symbols except those. Derivative keeps track of symbols with respect to which it will perform a derivative; those are bound variables, too, so it has its own symbols method. Any other method that uses bound variables should implement a symbols method.""" union = set.union return reduce(union, [arg.free_symbols for arg in self.args], set())
def _find_dynamicsymbols(self, inlist, insyms=[]): """Finds all non-supplied dynamicsymbols in the expressions.""" from sympy.core.function import AppliedUndef, Derivative t = dynamicsymbols._t return reduce(set.union, [set([i]) for j in inlist for i in j.atoms(AppliedUndef, Derivative) if i.atoms() == set([t])], set()) - insyms temp_f = set().union(*[i.atoms(AppliedUndef) for i in inlist]) temp_d = set().union(*[i.atoms(Derivative) for i in inlist]) set_f = set([a for a in temp_f if a.args == (t,)]) set_d = set([a for a in temp_d if ((a.args[0] in set_f) and all([i == t for i in a.variables]))]) return list(set.union(set_f, set_d) - set(insyms))
def weak_normalizer(a, d, DE, z=None): """ Weak normalization. Explanation =========== Given a derivation D on k[t] and f == a/d in k(t), return q in k[t] such that f - Dq/q is weakly normalized with respect to t. f in k(t) is said to be "weakly normalized" with respect to t if residue_p(f) is not a positive integer for any normal irreducible p in k[t] such that f is in R_p (Definition 6.1.1). If f has an elementary integral, this is equivalent to no logarithm of integral(f) whose argument depends on t has a positive integer coefficient, where the arguments of the logarithms not in k(t) are in k[t]. Returns (q, f - Dq/q) """ z = z or Dummy('z') dn, ds = splitfactor(d, DE) # Compute d1, where dn == d1*d2**2*...*dn**n is a square-free # factorization of d. g = gcd(dn, dn.diff(DE.t)) d_sqf_part = dn.quo(g) d1 = d_sqf_part.quo(gcd(d_sqf_part, g)) a1, b = gcdex_diophantine( d.quo(d1).as_poly(DE.t), d1.as_poly(DE.t), a.as_poly(DE.t)) r = (a - Poly(z, DE.t) * derivation(d1, DE)).as_poly(DE.t).resultant( d1.as_poly(DE.t)) r = Poly(r, z) if not r.expr.has(z): return (Poly(1, DE.t), (a, d)) N = [i for i in r.real_roots() if i in ZZ and i > 0] q = reduce(mul, [gcd(a - Poly(n, DE.t) * derivation(d1, DE), d1) for n in N], Poly(1, DE.t)) dq = derivation(q, DE) sn = q * a - d * dq sd = q * d sn, sd = sn.cancel(sd, include=True) return (q, (sn, sd))
def eval(cls, x, k): x = sympify(x) k = sympify(k) if x is S.NaN or k is S.NaN: return S.NaN elif k.is_integer and x == k: return factorial(x) elif k.is_Integer: if k is S.Zero: return S.One else: if k.is_positive: if x is S.Infinity: return S.Infinity elif x is S.NegativeInfinity: if k.is_odd: return S.NegativeInfinity else: return S.Infinity else: try: F, opt = poly_from_expr(x) except PolificationFailed: return reduce(lambda r, i: r * (x - i), range(0, int(k)), 1) if len(opt.gens) > 1 or F.degree() <= 1: return reduce(lambda r, i: r * (x - i), range(0, int(k)), 1) else: v = opt.gens[0] return reduce( lambda r, i: r * (F.subs(v, v - i).expand()), range(0, int(k)), 1) else: if x is S.Infinity: return S.Infinity elif x is S.NegativeInfinity: return S.Infinity else: try: F, opt = poly_from_expr(x) except PolificationFailed: return 1 / reduce(lambda r, i: r * (x + i), range(1, abs(int(k)) + 1), 1) if len(opt.gens) > 1 or F.degree() <= 1: return 1 / reduce(lambda r, i: r * (x + i), range(1, abs(int(k)) + 1), 1) else: v = opt.gens[0] return 1 / reduce( lambda r, i: r * (F.subs(v, v + i).expand()), range(1, abs(int(k)) + 1), 1)
def dynamicsymbols(names, level=0): """Uses symbols and Function for functions of time. Creates a SymPy UndefinedFunction, which is then initialized as a function of a variable, the default being Symbol('t'). Parameters ========== names : str Names of the dynamic symbols you want to create; works the same way as inputs to symbols level : int Level of differentiation of the returned function; d/dt once of t, twice of t, etc. Examples ======== >>> from sympy.physics.vector import dynamicsymbols >>> from sympy import diff, Symbol >>> q1 = dynamicsymbols('q1') >>> q1 q1(t) >>> diff(q1, Symbol('t')) Derivative(q1(t), t) """ esses = symbols(names, cls=Function) t = dynamicsymbols._t if hasattr(esses, '__iter__'): esses = [reduce(diff, [t] * level, e(t)) for e in esses] return esses else: return reduce(diff, [t] * level, esses(t))
def can_transf_matrix(self): """ Useless method, kept for compatibility with previous versions. DO NOT USE. Return the canonical transformation matrix from the canonical to the base dimension basis. It is the inverse of the matrix computed with inv_can_transf_matrix(). """ #TODO: the inversion will fail if the system is inconsistent, for # example if the matrix is not a square return reduce(lambda x, y: x.row_join(y), [self.dim_can_vector(d) for d in sorted(self.base_dims, key=str)] ).inv()
def root_factors(f, *gens, **args): """ Returns all factors of a univariate polynomial. Examples ======== >>> from sympy.abc import x, y >>> from sympy.polys.polyroots import root_factors >>> root_factors(x**2 - y, x) [x - sqrt(y), x + sqrt(y)] """ args = dict(args) filter = args.pop('filter', None) F = Poly(f, *gens, **args) if not F.is_Poly: return [f] if F.is_multivariate: raise ValueError('multivariate polynomials are not supported') x = F.gens[0] zeros = roots(F, filter=filter) if not zeros: factors = [F] else: factors, N = [], 0 for r, n in ordered(zeros.items()): factors, N = factors + [Poly(x - r, x)]*n, N + n if N < F.degree(): G = reduce(lambda p, q: p*q, factors) factors.append(F.quo(G)) if not isinstance(f, Poly): factors = [ f.as_expr() for f in factors ] return factors
def _get_indices_Add(expr): """Determine outer indices of an Add object. In a sum, each term must have the same set of outer indices. A valid expression could be x(i)*y(j) - x(j)*y(i) But we do not allow expressions like: x(i)*y(j) - z(j)*z(j) FIXME: Add support for Numpy broadcasting Examples ======== >>> from sympy.tensor.index_methods import _get_indices_Add >>> from sympy.tensor.indexed import IndexedBase, Idx >>> i, j, k = map(Idx, ['i', 'j', 'k']) >>> x = IndexedBase('x') >>> y = IndexedBase('y') >>> _get_indices_Add(x[i] + x[k]*y[i, k]) ({i}, {}) """ inds = list(map(get_indices, expr.args)) inds, syms = list(zip(*inds)) # allow broadcast of scalars non_scalars = [x for x in inds if x != set()] if not non_scalars: return set(), {} if not all([x == non_scalars[0] for x in non_scalars[1:]]): raise IndexConformanceException("Indices are not consistent: %s" % expr) if not reduce(lambda x, y: x != y or y, syms): symmetries = syms[0] else: # FIXME: search for symmetries symmetries = {} return non_scalars[0], symmetries
def And(*args): """Defines the three valued ``And`` behaviour for a 2-tuple of three valued logic values""" def reduce_and(cmp_intervala, cmp_intervalb): if cmp_intervala[0] is False or cmp_intervalb[0] is False: first = False elif cmp_intervala[0] is None or cmp_intervalb[0] is None: first = None else: first = True if cmp_intervala[1] is False or cmp_intervalb[1] is False: second = False elif cmp_intervala[1] is None or cmp_intervalb[1] is None: second = None else: second = True return (first, second) return reduce(reduce_and, args)
def inv_can_transf_matrix(self): """ Compute the inverse transformation matrix from the base to the canonical dimension basis. It corresponds to the matrix where columns are the vector of base dimensions in canonical basis. This matrix will almost never be used because dimensions are always define with respect to the canonical basis, so no work has to be done to get them in this basis. Nonetheless if this matrix is not square (or not invertible) it means that we have chosen a bad basis. """ matrix = reduce(lambda x, y: x.row_join(y), [self.dim_can_vector(d) for d in self._base_dims]) return matrix
def _module_quotient(self, other, relations=False): # See: [SCA, section 2.8.4] if relations and len(other.gens) != 1: raise NotImplementedError if len(other.gens) == 0: return self.ring.ideal(1) elif len(other.gens) == 1: # We do some trickery. Let f be the (vector!) generating ``other`` # and f1, .., fn be the (vectors) generating self. # Consider the submodule of R^{r+1} generated by (f, 1) and # {(fi, 0) | i}. Then the intersection with the last module # component yields the quotient. g1 = list(other.gens[0]) + [1] gi = [list(x) + [0] for x in self.gens] # NOTE: We *need* to use an elimination order M = self.ring.free_module(self.rank + 1).submodule( *([g1] + gi), order="ilex", TOP=False ) if not relations: return self.ring.ideal( *[ x[-1] for x in M._groebner_vec() if all(y == self.ring.zero for y in x[:-1]) ] ) else: G, R = M._groebner_vec(extended=True) indices = [ i for i, x in enumerate(G) if all(y == self.ring.zero for y in x[:-1]) ] return ( self.ring.ideal(*[G[i][-1] for i in indices]), [[-x for x in R[i][1:]] for i in indices], ) # For more generators, we use I : <h1, .., hn> = intersection of # {I : <hi> | i} # TODO this can be done more efficiently return reduce( lambda x, y: x.intersect(y), (self._module_quotient(self.container.submodule(x)) for x in other.gens), )
def _eval_matrix_mul(self, other): from sympy import Add # cache attributes for faster access self_rows, self_cols = self.rows, self.cols other_rows, other_cols = other.rows, other.cols other_len = other_rows * other_cols new_mat_rows = self_rows if other.rows == 1: new_mat_cols = other.rows other_rows, other_cols = other_cols, other_rows else: new_mat_cols = other.cols # preallocate the array new_mat = [self.zero] * new_mat_rows * new_mat_cols # if we multiply an n x 0 with a 0 x m, the # expected behavior is to produce an n x m matrix of zeros if self_cols != 0 and other_rows != 0: # cache self._args and other._args for performance mat = self._args other_mat = other._args for i in range(len(new_mat)): row, col = i // new_mat_cols, i % new_mat_cols row_indices = range(self_cols * row, self_cols * (row + 1)) col_indices = range(col, other_len, other_cols) vec = (mat[a] * other_mat[b] for a, b in zip(row_indices, col_indices)) try: new_mat[i] = Add(*vec) except (TypeError, SympifyError): # Block matrices don't work with `sum` or `Add` (ISSUE #11599) # They don't work with `sum` because `sum` tries to add `0` # initially, and for a matrix, that is a mix of a scalar and # a matrix, which raises a TypeError. Fall back to a # block-matrix-safe way to multiply if the `sum` fails. vec = (mat[a] * other_mat[b] for a, b in zip(row_indices, col_indices)) new_mat[i] = reduce(lambda a, b: a + b, vec) return classof(self, other)._new(new_mat_rows, new_mat_cols, new_mat, copy=False)
def find_pure_symbol_int_repr(symbols, unknown_clauses): """ Same as find_pure_symbol, but arguments are expected to be in integer representation >>> from sympy.logic.algorithms.dpll import find_pure_symbol_int_repr >>> find_pure_symbol_int_repr(set([1,2,3]), ... [set([1, -2]), set([-2, -3]), set([3, 1])]) (1, True) """ all_symbols = reduce(set.union, unknown_clauses, set()) found_pos = all_symbols.intersection(symbols) found_neg = all_symbols.intersection([-s for s in symbols]) for p in found_pos: if -p not in found_neg: return p, True for p in found_neg: if -p not in found_pos: return -p, False return None, None
def combine(c1, c2): """Return the tuple (a, m) which satisfies the requirement that n = a + i*m satisfy n = a1 + j*m1 and n = a2 = k*m2. References ========== - http://en.wikipedia.org/wiki/Method_of_successive_substitution """ a1, m1 = c1 a2, m2 = c2 a, b, c = m1, a2 - a1, m2 g = reduce(igcd, [a, b, c]) a, b, c = [i // g for i in [a, b, c]] if a != 1: inv_a, _, g = igcdex(a, c) if g != 1: return None b *= inv_a a, m = a1 + m1 * b, m1 * c return a, m
def merge_explicit_mul(vecmul): """ Merge explicit Vector and Expr (Numbers, Symbols, ...) arguments Example ======== >>> from sympy.vector import CoordSys3D >>> v1 = VectorSymbol("v1") >>> v2 = VectorSymbol("v2") >>> C = CoordSys3D("C") >>> vn1 = R.x * R.i + R.y * R.j + R.k * R.z >>> expr = VecMul(2, v1.mag, vn1, x, evaluate=False) >>> pprint(expr) 2*x*(R.x*R.i + R.y*R.j + R.z*R.k)*Magnitude(VectorSymbol(v1)) >>> pprint(merge_explicit(expr)) (2*R.x*x*R.i + 2*R.y*x*R.j + 2*R.z*x*R.k)*Magnitude(VectorSymbol(v1)) """ groups = sift(vecmul.args, lambda arg: not isinstance(arg, VectorExpr)) print("\t", groups) if len(groups[True]) > 1: return VecMul(*(groups[False] + [reduce(mul, groups[True])])) else: return vecmul
def merge_explicit(matadd): """ Merge explicit MatrixBase arguments >>> from sympy import MatrixSymbol, eye, Matrix, MatAdd, pprint >>> from sympy.matrices.expressions.matadd import merge_explicit >>> A = MatrixSymbol('A', 2, 2) >>> B = eye(2) >>> C = Matrix([[1, 2], [3, 4]]) >>> X = MatAdd(A, B, C) >>> pprint(X) [1 0] [1 2] A + [ ] + [ ] [0 1] [3 4] >>> pprint(merge_explicit(X)) [2 2] A + [ ] [3 5] """ groups = sift(matadd.args, lambda arg: isinstance(arg, MatrixBase)) if len(groups[True]) > 1: return SuperMatAdd(*(groups[False] + [reduce(add, groups[True])])) else: return matadd
def _get_indices_Mul(expr, return_dummies=False): """Determine the outer indices of a Mul object. Examples ======== >>> from sympy.tensor.index_methods import _get_indices_Mul >>> from sympy.tensor.indexed import IndexedBase, Idx >>> i, j, k = map(Idx, ['i', 'j', 'k']) >>> x = IndexedBase('x') >>> y = IndexedBase('y') >>> _get_indices_Mul(x[i, k]*y[j, k]) ({i, j}, {}) >>> _get_indices_Mul(x[i, k]*y[j, k], return_dummies=True) ({i, j}, {}, (k,)) """ inds = list(map(get_indices, expr.args)) inds, syms = list(zip(*inds)) inds = list(map(list, inds)) inds = list(reduce(lambda x, y: x + y, inds)) inds, dummies = _remove_repeated(inds) symmetry = {} for s in syms: for pair in s: if pair in symmetry: symmetry[pair] *= s[pair] else: symmetry[pair] = s[pair] if return_dummies: return inds, symmetry, dummies else: return inds, symmetry
def _eval_matrix_mul(self, other): """Fast multiplication exploiting the sparsity of the matrix.""" if not isinstance(other, SparseMatrix): other = self._new(other) # if we made it here, we're both sparse matrices # create quick lookups for rows and cols row_lookup = defaultdict(dict) for (i, j), val in self._smat.items(): row_lookup[i][j] = val col_lookup = defaultdict(dict) for (i, j), val in other._smat.items(): col_lookup[j][i] = val smat = {} for row in row_lookup.keys(): for col in col_lookup.keys(): # find the common indices of non-zero entries. # these are the only things that need to be multiplied. indices = set(col_lookup[col].keys()) & set( row_lookup[row].keys()) if indices: vec = [ row_lookup[row][k] * col_lookup[col][k] for k in indices ] try: smat[row, col] = Add(*vec) except (TypeError, SympifyError): # Some matrices don't work with `sum` or `Add` # They don't work with `sum` because `sum` tries to add `0` # Fall back to a safe way to multiply if the `Add` fails. smat[row, col] = reduce(lambda a, b: a + b, vec) return self._new(self.rows, other.cols, smat)
def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3): """ Compute indefinite integral using heuristic Risch algorithm. This is a heuristic approach to indefinite integration in finite terms using the extended heuristic (parallel) Risch algorithm, based on Manuel Bronstein's "Poor Man's Integrator". The algorithm supports various classes of functions including transcendental elementary or special functions like Airy, Bessel, Whittaker and Lambert. Note that this algorithm is not a decision procedure. If it isn't able to compute the antiderivative for a given function, then this is not a proof that such a functions does not exist. One should use recursive Risch algorithm in such case. It's an open question if this algorithm can be made a full decision procedure. This is an internal integrator procedure. You should use toplevel 'integrate' function in most cases, as this procedure needs some preprocessing steps and otherwise may fail. Specification ============= heurisch(f, x, rewrite=False, hints=None) where f : expression x : symbol rewrite -> force rewrite 'f' in terms of 'tan' and 'tanh' hints -> a list of functions that may appear in anti-derivate - hints = None --> no suggestions at all - hints = [ ] --> try to figure out - hints = [f1, ..., fn] --> we know better Examples ======== >>> from sympy import tan >>> from sympy.integrals.heurisch import heurisch >>> from sympy.abc import x, y >>> heurisch(y*tan(x), x) y*log(tan(x)**2 + 1)/2 See Manuel Bronstein's "Poor Man's Integrator": [1] http://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/index.html For more information on the implemented algorithm refer to: [2] K. Geddes, L. Stefanus, On the Risch-Norman Integration Method and its Implementation in Maple, Proceedings of ISSAC'89, ACM Press, 212-217. [3] J. H. Davenport, On the Parallel Risch Algorithm (I), Proceedings of EUROCAM'82, LNCS 144, Springer, 144-157. [4] J. H. Davenport, On the Parallel Risch Algorithm (III): Use of Tangents, SIGSAM Bulletin 16 (1982), 3-6. [5] J. H. Davenport, B. M. Trager, On the Parallel Risch Algorithm (II), ACM Transactions on Mathematical Software 11 (1985), 356-362. See Also ======== sympy.integrals.integrals.Integral.doit sympy.integrals.integrals.Integral components """ f = sympify(f) if not f.is_Add: indep, f = f.as_independent(x) else: indep = S.One if not f.has(x): return indep * f * x rewritables = { (sin, cos, cot): tan, (sinh, cosh, coth): tanh, } if rewrite: for candidates, rule in rewritables.iteritems(): f = f.rewrite(candidates, rule) else: for candidates in rewritables.iterkeys(): if f.has(*candidates): break else: rewrite = True terms = components(f, x) if hints is not None: if not hints: a = Wild('a', exclude=[x]) b = Wild('b', exclude=[x]) c = Wild('c', exclude=[x]) for g in set(terms): if g.is_Function: if g.func is exp: M = g.args[0].match(a * x**2) if M is not None: terms.add(erf(sqrt(-M[a]) * x)) M = g.args[0].match(a * x**2 + b * x + c) if M is not None: if M[a].is_positive: terms.add( sqrt(pi / 4 * (-M[a])) * exp(M[c] - M[b]**2 / (4 * M[a])) * erf(-sqrt(-M[a]) * x + M[b] / (2 * sqrt(-M[a])))) elif M[a].is_negative: terms.add( sqrt(pi / 4 * (-M[a])) * exp(M[c] - M[b]**2 / (4 * M[a])) * erf( sqrt(-M[a]) * x - M[b] / (2 * sqrt(-M[a])))) M = g.args[0].match(a * log(x)**2) if M is not None: if M[a].is_positive: terms.add(-I * erf(I * (sqrt(M[a]) * log(x) + 1 / (2 * sqrt(M[a]))))) if M[a].is_negative: terms.add( erf( sqrt(-M[a]) * log(x) - 1 / (2 * sqrt(-M[a])))) elif g.is_Pow: if g.exp.is_Rational and g.exp.q == 2: M = g.base.match(a * x**2 + b) if M is not None and M[b].is_positive: if M[a].is_positive: terms.add(asinh(sqrt(M[a] / M[b]) * x)) elif M[a].is_negative: terms.add(asin(sqrt(-M[a] / M[b]) * x)) M = g.base.match(a * x**2 - b) if M is not None and M[b].is_positive: if M[a].is_positive: terms.add(acosh(sqrt(M[a] / M[b]) * x)) elif M[a].is_negative: terms.add((-M[b] / 2 * sqrt(-M[a]) * atan( sqrt(-M[a]) * x / sqrt(M[a] * x**2 - M[b])) )) else: terms |= set(hints) for g in set(terms): terms |= components(cancel(g.diff(x)), x) # TODO: caching is significant factor for why permutations work at all. Change this. V = _symbols('x', len(terms)) mapping = dict(zip(terms, V)) rev_mapping = {} for k, v in mapping.iteritems(): rev_mapping[v] = k if mappings is None: # Pre-sort mapping in order of largest to smallest expressions (last is always x). def _sort_key(arg): return default_sort_key(arg[0].as_independent(x)[1]) mapping = sorted(mapping.items(), key=_sort_key, reverse=True) mappings = permutations(mapping) def _substitute(expr): return expr.subs(mapping) for mapping in mappings: # TODO: optimize this by not generating permutations where mapping[-1] != x. if mapping[-1][0] != x: continue mapping = list(mapping) diffs = [_substitute(cancel(g.diff(x))) for g in terms] denoms = [g.as_numer_denom()[1] for g in diffs] if all(h.is_polynomial(*V) for h in denoms) and _substitute(f).is_rational_function(*V): denom = reduce(lambda p, q: lcm(p, q, *V), denoms) break else: if not rewrite: result = heurisch(f, x, rewrite=True, hints=hints) if result is not None: return indep * result return None numers = [cancel(denom * g) for g in diffs] def _derivation(h): return Add(*[d * h.diff(v) for d, v in zip(numers, V)]) def _deflation(p): for y in V: if not p.has(y): continue if _derivation(p) is not S.Zero: c, q = p.as_poly(y).primitive() return _deflation(c) * gcd(q, q.diff(y)).as_expr() else: return p def _splitter(p): for y in V: if not p.has(y): continue if _derivation(y) is not S.Zero: c, q = p.as_poly(y).primitive() q = q.as_expr() h = gcd(q, _derivation(q), y) s = quo(h, gcd(q, q.diff(y), y), y) c_split = _splitter(c) if s.as_poly(y).degree() == 0: return (c_split[0], q * c_split[1]) q_split = _splitter(cancel(q / s)) return (c_split[0] * q_split[0] * s, c_split[1] * q_split[1]) else: return (S.One, p) special = {} for term in terms: if term.is_Function: if term.func is tan: special[1 + _substitute(term)**2] = False elif term.func is tanh: special[1 + _substitute(term)] = False special[1 - _substitute(term)] = False elif term.func is C.LambertW: special[_substitute(term)] = True F = _substitute(f) P, Q = F.as_numer_denom() u_split = _splitter(denom) v_split = _splitter(Q) polys = list(v_split) + [u_split[0]] + special.keys() s = u_split[0] * Mul(*[k for k, v in special.iteritems() if v]) polified = [p.as_poly(*V) for p in [s, P, Q]] if None in polified: return None a, b, c = [p.total_degree() for p in polified] poly_denom = (s * v_split[0] * _deflation(v_split[1])).as_expr() def _exponent(g): if g.is_Pow: if g.exp.is_Rational and g.exp.q != 1: if g.exp.p > 0: return g.exp.p + g.exp.q - 1 else: return abs(g.exp.p + g.exp.q) else: return 1 elif not g.is_Atom and g.args: return max([_exponent(h) for h in g.args]) else: return 1 A, B = _exponent(f), a + max(b, c) if A > 1 and B > 1: monoms = monomials(V, A + B - 1) else: monoms = monomials(V, A + B) poly_coeffs = _symbols('A', len(monoms)) poly_part = Add( *[poly_coeffs[i] * monomial for i, monomial in enumerate(monoms)]) reducibles = set() for poly in polys: if poly.has(*V): try: factorization = factor(poly, greedy=True) except PolynomialError: factorization = poly factorization = poly if factorization.is_Mul: reducibles |= set(factorization.args) else: reducibles.add(factorization) def _integrate(field=None): irreducibles = set() for poly in reducibles: for z in poly.atoms(Symbol): if z in V: break else: continue irreducibles |= set(root_factors(poly, z, filter=field)) log_coeffs, log_part = [], [] B = _symbols('B', len(irreducibles)) for i, poly in enumerate(irreducibles): if poly.has(*V): log_coeffs.append(B[i]) log_part.append(log_coeffs[-1] * log(poly)) coeffs = poly_coeffs + log_coeffs candidate = poly_part / poly_denom + Add(*log_part) h = F - _derivation(candidate) / denom numer = h.as_numer_denom()[0].expand(force=True) equations = defaultdict(lambda: S.Zero) for term in Add.make_args(numer): coeff, dependent = term.as_independent(*V) equations[dependent] += coeff solution = solve(equations.values(), *coeffs) return (solution, candidate, coeffs) if solution else None if not (F.atoms(Symbol) - set(V)): result = _integrate('Q') if result is None: result = _integrate() else: result = _integrate() if result is not None: (solution, candidate, coeffs) = result antideriv = candidate.subs(solution) for coeff in coeffs: if coeff not in solution: antideriv = antideriv.subs(coeff, S.Zero) antideriv = antideriv.subs(rev_mapping) antideriv = cancel(antideriv).expand(force=True) if antideriv.is_Add: antideriv = antideriv.as_independent(x)[1] return indep * antideriv else: if retries >= 0: result = heurisch(f, x, mappings=mappings, rewrite=rewrite, hints=hints, retries=retries - 1) if result is not None: return indep * result return None