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 = [] 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 icoeff, iterms = sqf_list(i) l.append( Mul(*([Pow(icoeff, j)] + [Pow(b, e * j) for b, e in iterms]))) const = cancel(fa.as_expr() / fd.as_expr() / Mul(*l)) return (ans, result, const)
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 _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( *list(map(Determinant, square_matrices)))
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 __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(v.is_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 any(x in p.free_symbols for x in variables for p in point): raise ValueError('Got %s as a point.' % point) if variables: if any(p != point[0] for p in point): raise NotImplementedError( "Multivariable orders at different points are not supported.") if point[0] is S.Infinity: s = {k: 1/Dummy() for k in variables} rs = {1/v: 1/k for k, v in s.items()} ps = [S.Zero for p in point] elif point[0] is S.NegativeInfinity: s = {k: -1/Dummy() for k in variables} rs = {-1/v: -1/k for k, v in s.items()} ps = [S.Zero for p in point] elif point[0] is not S.Zero: s = {k: Dummy() + point[0] for k in variables} rs = {(v - point[0]).together(): k - point[0] for k, v in s.items()} ps = [S.Zero for p in point] else: s = () rs = () ps = list(point) expr = expr.subs(s) if expr.is_Add: expr = expr.factor() if s: args = tuple([r[0] for r in rs.items()]) else: args = tuple(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() old_expr = None while old_expr != expr: old_expr = expr if expr.is_Add: lst = expr.extract_leading_order(args) expr = Add(*[f.expr for (e, f) in lst]) elif expr: try: expr = expr.as_leading_term(*args) except PoleError: if isinstance(expr, Function) or\ all(isinstance(arg, Function) for arg in expr.args): # It is not possible to simplify an expression # containing only functions (which raise error on # call to leading term) further pass else: orders = [] pts = tuple(zip(args, ps)) for arg in expr.args: try: lt = arg.as_leading_term(*args) except PoleError: lt = arg if lt not in args: order = Order(lt) else: order = Order(lt, *pts) orders.append(order) if expr.is_Add: new_expr = Order(Add(*orders), *pts) if new_expr.is_Add: new_expr = Order(Add(*[a.expr for a in new_expr.args]), *pts) expr = new_expr.expr elif expr.is_Mul: expr = Mul(*[a.expr for a in orders]) elif expr.is_Pow: e = expr.exp b = expr.base expr = exp(e * log(b)) # It would probably be better to handle this somewhere # else. This is needed for a testcase in which there is a # symbol with the assumptions zero=True. if expr.is_zero: expr = S.Zero else: expr = expr.as_independent(*args, as_Add=False)[1] expr = expand_power_base(expr) expr = expand_log(expr) if len(args) == 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 = args[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) expr = expr.subs(rs) if expr.is_Order: expr = expr.expr if not expr.has(*variables) and not expr.is_zero: expr = S.One # create Order instance: vp = dict(zip(variables, point)) variables.sort(key=default_sort_key) point = [vp[v] for v in variables] args = (expr,) + Tuple(*zip(variables, point)) obj = Expr.__new__(cls, *args) return obj
def itermonomials(variables, max_degrees, min_degrees=None): r""" `max_degrees` and `min_degrees` are either both integers or both lists. Unless otherwise specified, `min_degrees` is either 0 or [0,...,0]. A generator of all monomials `monom` is returned, such that either min_degree <= total_degree(monom) <= max_degree, or min_degrees[i] <= degree_list(monom)[i] <= max_degrees[i], for all i. Case I:: `max_degrees` and `min_degrees` are both integers. =========================================================== Given a set of variables `V` and a min_degree `N` and a max_degree `M` generate a set of monomials of degree less than or equal to `N` and greater than or equal to `M`. The total number of monomials in commutative variables is huge and is given by the following formula if `M = 0`: .. math:: \frac{(\#V + N)!}{\#V! N!} For example if we would like to generate a dense polynomial of a total degree `N = 50` and `M = 0`, which is the worst case, in 5 variables, assuming that exponents and all of coefficients are 32-bit long and stored in an array we would need almost 80 GiB of memory! Fortunately most polynomials, that we will encounter, are sparse. Examples ======== Consider monomials in commutative variables `x` and `y` and non-commutative variables `a` and `b`:: >>> from sympy import symbols >>> from sympy.polys.monomials import itermonomials >>> from sympy.polys.orderings import monomial_key >>> from sympy.abc import x, y >>> sorted(itermonomials([x, y], 2), key=monomial_key('grlex', [y, x])) [1, x, y, x**2, x*y, y**2] >>> sorted(itermonomials([x, y], 3), key=monomial_key('grlex', [y, x])) [1, x, y, x**2, x*y, y**2, x**3, x**2*y, x*y**2, y**3] >>> a, b = symbols('a, b', commutative=False) >>> set(itermonomials([a, b, x], 2)) {1, a, a**2, b, b**2, x, x**2, a*b, b*a, x*a, x*b} >>> sorted(itermonomials([x, y], 2, 1), key=monomial_key('grlex', [y, x])) [x, y, x**2, x*y, y**2] Case II:: `max_degrees` and `min_degrees` are both lists. ========================================================= If max_degrees = [d_1, ..., d_n] and min_degrees = [e_1, ..., e_n], the number of monomials generated is: (d_1 - e_1 + 1) * ... * (d_n - e_n + 1) Example ======= Let us generate all monomials `monom` in variables `x`, and `y` such that [1, 2][i] <= degree_list(monom)[i] <= [2, 4][i], i = 0, 1 :: >>> from sympy import symbols >>> from sympy.polys.monomials import itermonomials >>> from sympy.polys.orderings import monomial_key >>> from sympy.abc import x, y >>> sorted(itermonomials([x, y], [2, 4], [1, 2]), reverse=True, key=monomial_key('lex', [x, y])) [x**2*y**4, x**2*y**3, x**2*y**2, x*y**4, x*y**3, x*y**2] """ n = len(variables) if is_sequence(max_degrees): if len(max_degrees) != n: raise ValueError('Argument sizes do not match') if min_degrees is None: min_degrees = [0]*n elif not is_sequence(min_degrees): raise ValueError('min_degrees is not a list') else: if len(min_degrees) != n: raise ValueError('Argument sizes do not match') if any(i < 0 for i in min_degrees): raise ValueError("min_degrees can't contain negative numbers") total_degree = False else: max_degree = max_degrees if max_degree < 0: raise ValueError("max_degrees can't be negative") if min_degrees is None: min_degree = 0 else: if min_degrees < 0: raise ValueError("min_degrees can't be negative") min_degree = min_degrees total_degree = True if total_degree: if min_degree > max_degree: return if not variables or max_degree == 0: yield S.One return # Force to list in case of passed tuple or other incompatible collection variables = list(variables) + [S.One] if all(variable.is_commutative for variable in variables): monomials_list_comm = [] for item in combinations_with_replacement(variables, max_degree): powers = dict() for variable in variables: powers[variable] = 0 for variable in item: if variable != 1: powers[variable] += 1 if max(powers.values()) >= min_degree: monomials_list_comm.append(Mul(*item)) for mon in set(monomials_list_comm): yield mon else: monomials_list_non_comm = [] for item in product(variables, repeat=max_degree): powers = dict() for variable in variables: powers[variable] = 0 for variable in item: if variable != 1: powers[variable] += 1 if max(powers.values()) >= min_degree: monomials_list_non_comm.append(Mul(*item)) for mon in set(monomials_list_non_comm): yield mon else: if any(min_degrees[i] > max_degrees[i] for i in range(n)): raise ValueError('min_degrees[i] must be <= max_degrees[i] for all i') power_lists = [] for var, min_d, max_d in zip(variables, min_degrees, max_degrees): power_lists.append([var**i for i in range(min_d, max_d + 1)]) for powers in product(*power_lists): yield Mul(*powers)
def __mul__(self, other): if isinstance(other, Expr): if isinstance(other, AccumBounds): return AccumBounds( Min(Mul(self.min, other.min), Mul(self.min, other.max), Mul(self.max, other.min), Mul(self.max, other.max)), Max(Mul(self.min, other.min), Mul(self.min, other.max), Mul(self.max, other.min), Mul(self.max, other.max))) if other is S.Infinity: if self.min.is_zero: return AccumBounds(0, oo) if self.max.is_zero: return AccumBounds(-oo, 0) if other is S.NegativeInfinity: if self.min.is_zero: return AccumBounds(-oo, 0) if self.max.is_zero: return AccumBounds(0, oo) if other.is_real: if other.is_zero: if self == AccumBounds(-oo, oo): return AccumBounds(-oo, oo) if self.max is S.Infinity: return AccumBounds(0, oo) if self.min is S.NegativeInfinity: return AccumBounds(-oo, 0) return S.Zero if other.is_positive: return AccumBounds(Mul(self.min, other), Mul(self.max, other)) elif other.is_negative: return AccumBounds(Mul(self.max, other), Mul(self.min, other)) if isinstance(other, Order): return other return Mul(self, other, evaluate=False) return NotImplemented
def eval(cls, arg): from sympy.simplify.simplify import signsimp from sympy.core.function import expand_mul from sympy.core.power import Pow 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) n, d = arg.as_numer_denom() if d.free_symbols and not n.free_symbols: return cls(n) / cls(d) if arg.is_Mul: known = [] unk = [] for t in arg.args: if t.is_Pow and t.exp.is_integer and t.exp.is_negative: bnew = cls(t.base) if isinstance(bnew, cls): unk.append(t) else: known.append(Pow(bnew, t.exp)) else: tnew = cls(t) if isinstance(tnew, cls): unk.append(t) 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 S.ComplexInfinity: return S.Infinity if arg.is_Pow: base, exponent = arg.as_base_exp() if base.is_extended_real: if exponent.is_integer: if exponent.is_even: return arg if base is S.NegativeOne: return S.One return Abs(base)**exponent if base.is_extended_nonnegative: return base**re(exponent) if base.is_extended_negative: return (-base)**re(exponent) * exp(-S.Pi * im(exponent)) return elif not base.has(Symbol): # complex base # express base**exponent as exp(exponent*log(base)) a, b = log(base).as_real_imag() z = a + I * b return exp(re(exponent * z)) 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_extended_nonnegative: return arg if arg.is_extended_nonpositive: return -arg if arg.is_imaginary: arg2 = -S.ImaginaryUnit * arg if arg2.is_extended_nonnegative: return arg2 # reject result if all new conjugates are just wrappers around # an expression that was already in the arg conj = signsimp(arg.conjugate(), evaluate=False) 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({i: Dummy(real=True) for i in ignore}) unk = [ a for a in abs_free_arg.free_symbols if a.is_extended_real is None ] if not unk or not all(conj.has(conjugate(u)) for u in unk): return sqrt(expand_mul(arg * conj))
def _entry(self, i, j, **kwargs): return Mul(*[arg._entry(i, j, **kwargs) for arg in self.args])
def change_mul(node, x): """change_mul(node, x) Rearranges the operands of a product, bringing to front any simple DiracDelta expression. If no simple DiracDelta expression was found, then all the DiracDelta expressions are simplified (using DiracDelta.simplify). Return: (dirac, new node) Where: o dirac is either a simple DiracDelta expression or None (if no simple expression was found); o new node is either a simplified DiracDelta expressions or None (if it could not be simplified). Examples ======== >>> from sympy import DiracDelta, cos >>> from sympy.integrals.deltafunctions import change_mul >>> from sympy.abc import x, y >>> change_mul(x*y*DiracDelta(x)*cos(x), x) (DiracDelta(x), x*y*cos(x)) >>> change_mul(x*y*DiracDelta(x**2 - 1)*cos(x), x) (None, x*y*cos(x)*DiracDelta(x - 1)/2 + x*y*cos(x)*DiracDelta(x + 1)/2) >>> change_mul(x*y*DiracDelta(cos(x))*cos(x), x) (None, None) See Also ======== sympy.functions.special.delta_functions.DiracDelta deltaintegrate """ if not (node.is_Mul or node.is_Pow): return node new_args = [] dirac = None #Sorting is needed so that we consistently collapse the same delta; #However, we must preserve the ordering of non-commutative terms c, nc = node.args_cnc() sorted_args = sorted(c, key=default_sort_key) sorted_args.extend(nc) for arg in sorted_args: if arg.is_Pow and arg.base.func is DiracDelta: new_args.append(arg.func(arg.base, arg.exp - 1)) arg = arg.base if dirac is None and (arg.func is DiracDelta and arg.is_simple(x) and (len(arg.args) <= 1 or arg.args[1] == 0)): dirac = arg else: new_args.append(arg) if not dirac: # there was no simple dirac new_args = [] for arg in sorted_args: if arg.func is DiracDelta: new_args.append(arg.simplify(x)) elif arg.is_Pow and arg.base.func is DiracDelta: new_args.append(arg.func(arg.base.simplify(x), arg.exp)) else: new_args.append(change_mul(arg, x)) if new_args != sorted_args: nnode = Mul(*new_args).expand() else: # if the node didn't change there is nothing to do nnode = None return (None, nnode) return (dirac, Mul(*new_args))
def test_add(): with evaluate(False): p = oo - oo assert isinstance(p, Add) and p.args == (oo, -oo) p = 5 - oo assert isinstance(p, Add) and p.args == (-oo, 5) p = oo - 5 assert isinstance(p, Add) and p.args == (oo, -5) p = oo + 5 assert isinstance(p, Add) and p.args == (oo, 5) p = 5 + oo assert isinstance(p, Add) and p.args == (oo, 5) p = -oo + 5 assert isinstance(p, Add) and p.args == (-oo, 5) p = -5 - oo assert isinstance(p, Add) and p.args == (-oo, -5) 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.One + 1 == Add(1, 1) assert 1 + S.One == 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.One / 3) assert S.One / 3 * 6 == Mul(S.One / 3, 6) assert 9**S(2) == Pow(9, 2) assert S(2)**9 == Pow(2, 9) assert S(2) / 2 == Mul(2, S.One / 2) assert S.One / 2 * 2 == Mul(S.One / 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.One / 3) assert S.One / 3 * 6 == Mul(S.One / 3, 6) assert S.One / 3 + sqrt(3) == Add(S.One / 3, sqrt(3)) assert sqrt(3) + S.One / 3 == Add(sqrt(3), S.One / 3) assert S.One / 2 * 10.333 == Mul(S.One / 2, 10.333) assert 10.333 * S.One / 2 == Mul(10.333, S.One / 2) assert sqrt(2) * sqrt(2) == Mul(sqrt(2), sqrt(2)) assert S.One / 2 + x == Add(S.One / 2, x) assert x + S.One / 2 == Add(x, S.One / 2) assert S.One / x * x == Mul(S.One / x, x) assert x * S.One / x == Mul(x, S.One / x)
def eval(cls, *_args): """.""" if not _args: return if not (len(_args) == 2): raise ValueError('Expecting two arguments') left = _args[0] right = _args[1] # TODO add properties in the spirit of ExteriorDerivative # ... if isinstance(left, Add): args = [cls.eval(i, right) for i in left.args] return Add(*args) # ... # ... if isinstance(right, Add): args = [cls.eval(left, i) for i in right.args] return Add(*args) # ... # ... from now on, we construct left and right with some coeffs # return is done at the end alpha = S.One if isinstance(left, Mul): coeffs = [a for a in left.args if isinstance(a, _coeffs_registery)] vectors = [a for a in left.args if not (a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: b = Mul(*vectors) alpha *= a left = b if isinstance(right, Mul): coeffs = [ a for a in right.args if isinstance(a, _coeffs_registery) ] vectors = [a for a in right.args if not (a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: b = Mul(*vectors) alpha *= a right = b # ... return alpha * cls(left, right, evaluate=False)
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(v.is_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 any(x in p.free_symbols for x in variables for p in point): raise ValueError('Got %s as a point.' % point) if variables: if any(p != point[0] for p in point): raise NotImplementedError if point[0] is S.Infinity: s = {k: 1 / Dummy() for k in variables} rs = {1 / v: 1 / k for k, v in s.items()} elif point[0] is not S.Zero: s = dict((k, Dummy() + point[0]) for k in variables) rs = dict((v - point[0], k - point[0]) for k, v in s.items()) else: s = () rs = () expr = expr.subs(s) if expr.is_Add: from sympy import expand_multinomial expr = expand_multinomial(expr) if s: args = tuple([r[0] for r in rs.items()]) else: args = tuple(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(args) expr = Add(*[f.expr for (e, f) in lst]) elif expr: expr = expr.as_leading_term(*args) expr = expr.as_independent(*args, as_Add=False)[1] expr = expand_power_base(expr) expr = expand_log(expr) if len(args) == 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 = args[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) expr = expr.subs(rs) 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: vp = dict(zip(variables, point)) variables.sort(key=default_sort_key) point = [vp[v] for v in variables] args = (expr, ) + Tuple(*zip(variables, point)) obj = Expr.__new__(cls, *args) return obj
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 eval(cls, *_args): """.""" if not _args: return if not len(_args) == 1: raise ValueError('Expecting one argument') expr = _args[0] if isinstance(expr, Add): args = expr.args args = [cls.eval(a) for a in expr.args] return Add(*args) elif isinstance(expr, Mul): coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: if len(vectors) == 2: a,b = vectors # TODO remove try/except using regularity from space try: if isinstance(a, (Tuple, VectorTestFunction, VectorField)): f = b ; F = a return f*Div(F) + Dot(F, grad(f)) elif isinstance(b, (Tuple, VectorTestFunction, VectorField)): f = a ; F = b return f*Div(F) + Dot(F, grad(f)) except: return cls(Mul(*vectors), evaluate=False) b = cls(Mul(*vectors), evaluate=False) return Mul(a, b) elif isinstance(expr, Cross): a,b = expr._args return Dot(b, Curl(a)) - Dot(a, Curl(b)) elif isinstance(expr, Curl): return S.Zero # ... check consistency between space type and the operator if _is_sympde_atom(expr): if not isinstance(expr.space.kind, (UndefinedSpaceType, HdivSpaceType, H1SpaceType)): msg = '> Wrong space kind, given {}'.format(expr.space.kind) raise ArgumentTypeError(msg) # ... return cls(expr, evaluate=False)
def opt_cse(exprs, order='canonical'): """Find optimization opportunities in Adds, Muls, Pows and negative coefficient Muls Parameters ---------- exprs : list of sympy expressions The expressions to optimize. order : string, 'none' or 'canonical' The order by which Mul and Add arguments are processed. For large expressions where speed is a concern, use the setting order='none'. Returns ------- opt_subs : dictionary of expression substitutions The expression substitutions which can be useful to optimize CSE. Examples -------- >>> from sympy.simplify.cse_main import opt_cse >>> from sympy.abc import x >>> opt_subs = opt_cse([x**-2]) >>> print(opt_subs) {x**(-2): 1/(x**2)} """ from sympy.matrices import Matrix opt_subs = dict() adds = set() muls = set() seen_subexp = set() def _find_opts(expr): if expr.is_Atom or expr.is_Order: return if iterable(expr): list(map(_find_opts, expr)) return if expr in seen_subexp: return expr seen_subexp.add(expr) list(map(_find_opts, expr.args)) if _coeff_isneg(expr): neg_expr = -expr if not neg_expr.is_Atom: opt_subs[expr] = Mul(S.NegativeOne, neg_expr, evaluate=False) seen_subexp.add(neg_expr) expr = neg_expr if expr.is_Mul: muls.add(expr) elif expr.is_Add: adds.add(expr) elif expr.is_Pow: if _coeff_isneg(expr.exp): opt_subs[expr] = Pow(Pow(expr.base, -expr.exp), S.NegativeOne, evaluate=False) for e in exprs: if isinstance(e, Basic): _find_opts(e) ## Process Adds and commutative Muls def _match_common_args(Func, funcs): if order != 'none': funcs = list(ordered(funcs)) else: funcs = sorted(funcs, key=lambda x: len(x.args)) func_args = [set(e.args) for e in funcs] for i in xrange(len(func_args)): for j in xrange(i + 1, len(func_args)): com_args = func_args[i].intersection(func_args[j]) if len(com_args) > 1: com_func = Func(*com_args) # for all sets, replace the common symbols by the function # over them, to allow recursive matches diff_i = func_args[i].difference(com_args) func_args[i] = diff_i | set([com_func]) if diff_i: opt_subs[funcs[i]] = Func(Func(*diff_i), com_func, evaluate=False) diff_j = func_args[j].difference(com_args) func_args[j] = diff_j | set([com_func]) opt_subs[funcs[j]] = Func(Func(*diff_j), com_func, evaluate=False) for k in xrange(j + 1, len(func_args)): if not com_args.difference(func_args[k]): diff_k = func_args[k].difference(com_args) func_args[k] = diff_k | set([com_func]) opt_subs[funcs[k]] = Func(Func(*diff_k), com_func, evaluate=False) # split muls into commutative comutative_muls = set() for m in muls: c, nc = m.args_cnc(cset=True) if c: c_mul = Mul(*c) if nc: opt_subs[m] = Mul(c_mul, Mul(*nc), evaluate=False) if len(c) > 1: comutative_muls.add(c_mul) _match_common_args(Add, adds) _match_common_args(Mul, comutative_muls) return opt_subs
def _eval_product(self, term, limits): from sympy import summation (k, a, n) = limits if k not in term.free_symbols: return term**(n - a + 1) if a == n: return term.subs(k, a) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in xrange(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a - r, n - a + 1) Q *= n - r if len(all_roots) < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = Product(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p, (k, a, n)) q = self._eval_product(q, (k, a, n)) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = Product(arg, (k, a, n)).doit() return A * B elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return Product(evaluated, limits) else: return f
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.radsimp 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 _entry(self, i, j): return Mul(*[arg._entry(i, j) for arg in self.args])
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 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 eval(cls, *_args): """.""" if not _args: return if not len(_args) == 2: raise ValueError('Expecting two arguments') left,right = _args if (left == 0) or (right == 0): return 0 if isinstance(left, Add): args = [cls.eval(i, right) for i in left.args] return Add(*args) if isinstance(right, Add): args = [cls.eval(left, i) for i in right.args] return Add(*args) # ... from now on, we construct left and right with some coeffs # return is done at the end alpha = S.One if isinstance(left, Mul): coeffs = [a for a in left.args if isinstance(a, _coeffs_registery)] for a in left.args: if ( isinstance(a, Pow) and isinstance(a.base, _coeffs_registery) and isinstance(a.exp, _coeffs_registery) ): coeffs += [a] elif isinstance(a, (ScalarField, ScalarTestFunction)): coeffs += [a] vectors = [a for a in left.args if not(a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: b = Mul(*vectors) alpha *= a left = b if isinstance(right, Mul): coeffs = [a for a in right.args if isinstance(a, _coeffs_registery)] for a in right.args: if ( isinstance(a, Pow) and isinstance(a.base, _coeffs_registery) and isinstance(a.exp, _coeffs_registery) ): coeffs += [a] elif isinstance(a, (ScalarField, ScalarTestFunction)): coeffs += [a] vectors = [a for a in right.args if not(a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: b = Mul(*vectors) alpha *= a right = b # ... # ... this is a hack to ensure commutativity # TODO to be improved try: if str(right) < str(left): return alpha*cls(right, left, evaluate=False) except: pass # ... return alpha*cls(left, right, evaluate=False)
def test_MatMul_postprocessor(): z = zeros(2) z1 = ZeroMatrix(2, 2) assert Mul(0, z) == Mul(z, 0) in [z, z1] M = Matrix([[1, 2], [3, 4]]) Mx = Matrix([[x, 2 * x], [3 * x, 4 * x]]) assert Mul(x, M) == Mul(M, x) == Mx A = MatrixSymbol("A", 2, 2) assert Mul(A, M) == MatMul(A, M) assert Mul(M, A) == MatMul(M, A) # Scalars should be absorbed into constant matrices a = Mul(x, M, A) b = Mul(M, x, A) c = Mul(M, A, x) assert a == b == c == MatMul(Mx, A) a = Mul(x, A, M) b = Mul(A, x, M) c = Mul(A, M, x) assert a == b == c == MatMul(A, Mx) assert Mul(M, M) == M**2 assert Mul(A, M, M) == MatMul(A, M**2) assert Mul(M, M, A) == MatMul(M**2, A) assert Mul(M, A, M) == MatMul(M, A, M) assert Mul(A, x, M, M, x) == MatMul(A, Mx**2)
def eval(cls, *_args): """.""" if not _args: return if not len(_args) == 2: raise ValueError('Expecting two arguments') left,right = _args if (left == 0) or (right == 0): return 0 # ... if isinstance(left, Add): args = [cls.eval(i, right) for i in left.args] return Add(*args) # ... # ... if isinstance(right, Add): args = [cls.eval(left, i) for i in right.args] return Add(*args) # ... # ... from now on, we construct left and right with some coeffs # return is done at the end alpha = S.One if isinstance(left, Mul): coeffs = [a for a in left.args if isinstance(a, _coeffs_registery)] vectors = [a for a in left.args if not(a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: b = Mul(*vectors) alpha *= a left = b if isinstance(right, Mul): coeffs = [a for a in right.args if isinstance(a, _coeffs_registery)] vectors = [a for a in right.args if not(a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: b = Mul(*vectors) alpha *= a right = b # ... if isinstance(right, _coeffs_registery): return S.Zero # ... check consistency between space type and the operator # TODO add appropriate space types if _is_sympde_atom(right): if not isinstance(right.space.kind, UndefinedSpaceType): msg = '> Wrong space kind, given {}'.format(right.space.kind) raise ArgumentTypeError(msg) # ... return alpha*cls(left, right, evaluate=False)
def collect(expr, syms, func=None, evaluate=None, exact=False, distribute_order_term=True): """ Collect additive terms of an expression. 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, z 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 """ expr = sympify(expr) syms = list(syms) if iterable(syms) else [syms] if evaluate is None: evaluate = global_evaluate[0] 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 = dict([(key, func(val)) for key, val in collected.items()]) if evaluate: return Add(*[key * val for key, val in collected.items()]) else: return collected
def eval(cls, *_args): """.""" if not _args: return if not len(_args) == 1: raise ValueError('Expecting one argument') expr = _args[0] if isinstance(expr, Add): args = [cls.eval(a) for a in expr.args] return Add(*args) elif isinstance(expr, Mul): coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: try: if len(vectors) == 1: f = vectors[0] b = cls(f) elif len(vectors) == 2: f,g = vectors b = f*cls(g) + g*cls(f) else: left = vectors[0] right = Mul(*vectors[1:]) f_left = cls(left, evaluate=True) f_right = cls(right, evaluate=True) b = left * f_right + f_left * right except: b = cls(Mul(*vectors), evaluate=False) return Mul(a, b) elif isinstance(expr, Pow): # TODO: fix this for the case where e is not a number b = expr.base e = expr.exp return e*cls(b)*Pow(b, e-1) elif isinstance(expr, Dot): try: a,b = expr._args return Cross(a, Curl(b)) - Cross(Curl(a), b) + Convect(a,b) + Convect(b,a) except: return cls(expr, evaluate=False) elif isinstance(expr, _coeffs_registery): return S.Zero # ... check consistency between space type and the operator if _is_sympde_atom(expr): if not isinstance(expr.space.kind, (UndefinedSpaceType, H1SpaceType)): msg = '> Wrong space kind, given {}'.format(expr.space.kind) raise ArgumentTypeError(msg) # ... return cls(expr, evaluate=False)
def fraction(expr, exact=False): """Returns a pair with expression's numerator and denominator. If the given expression is not a fraction then this function will return the tuple (expr, 1). This function will not make any attempt to simplify nested fractions or to do any term rewriting at all. If only one of the numerator/denominator pair is needed then use numer(expr) or denom(expr) functions respectively. >>> from sympy import fraction, Rational, Symbol >>> from sympy.abc import x, y >>> fraction(x/y) (x, y) >>> fraction(x) (x, 1) >>> fraction(1/y**2) (1, y**2) >>> fraction(x*y/2) (x*y, 2) >>> fraction(Rational(1, 2)) (1, 2) This function will also work fine with assumptions: >>> k = Symbol('k', negative=True) >>> fraction(x * y**k) (x, y**(-k)) If we know nothing about sign of some exponent and 'exact' flag is unset, then structure this exponent's structure will be analyzed and pretty fraction will be returned: >>> from sympy import exp, Mul >>> fraction(2*x**(-y)) (2, x**y) >>> fraction(exp(-x)) (1, exp(x)) >>> fraction(exp(-x), exact=True) (exp(-x), 1) The `exact` flag will also keep any unevaluated Muls from being evaluated: >>> u = Mul(2, x + 1, evaluate=False) >>> fraction(u) (2*x + 2, 1) >>> fraction(u, exact=True) (2*(x + 1), 1) """ expr = sympify(expr) numer, denom = [], [] for term in Mul.make_args(expr): if term.is_commutative and (term.is_Pow or isinstance(term, exp)): b, ex = term.as_base_exp() if ex.is_negative: if ex is S.NegativeOne: denom.append(b) elif exact: if ex.is_constant(): denom.append(Pow(b, -ex)) else: numer.append(term) else: denom.append(Pow(b, -ex)) elif ex.is_positive: numer.append(term) elif not exact and ex.is_Mul: n, d = term.as_numer_denom() numer.append(n) denom.append(d) else: numer.append(term) elif term.is_Rational: n, d = term.as_numer_denom() numer.append(n) denom.append(d) else: numer.append(term) if exact: return Mul(*numer, evaluate=False), Mul(*denom, evaluate=False) else: return Mul(*numer), Mul(*denom)
def eval(cls, *_args): """.""" if not _args: return if not len(_args) == 1: raise ValueError('Expecting one argument') expr = _args[0] if isinstance(expr, Add): args = expr.args args = [cls.eval(a) for a in expr.args] return Add(*args) elif isinstance(expr, Mul): coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One if coeffs: a = Mul(*coeffs) b = S.One if vectors: if len(vectors) == 2: a,b = vectors if isinstance(a, (Tuple, VectorTestFunction, VectorField, Grad)): f = b ; F = a return f*Curl(F) + Cross(grad(f), F) elif isinstance(b, (Tuple, VectorTestFunction, VectorField, Grad)): f = a ; F = b return f*Curl(F) + Cross(grad(f), F) b = cls(Mul(*vectors), evaluate=False) return Mul(a, b) elif isinstance(expr, Cross): try: a,b = expr._args return a * Div(b) - b*Div(a) + Convect(b, a) - Convect(a, b) except: return cls(expr, evaluate=False) elif isinstance(expr, Grad): return S.Zero elif isinstance(expr, Curl): f = expr._args[0] return Grad(Div(f)) - Laplace(f) # ... check consistency between space type and the operator if _is_sympde_atom(expr): if not isinstance(expr.space.kind, (UndefinedSpaceType, HcurlSpaceType, H1SpaceType)): msg = '> Wrong space kind, given {}'.format(expr.space.kind) raise ArgumentTypeError(msg) # ... return cls(expr, evaluate=False)
def test_construction_with_Mul(): assert Mul(C, D) == MatMul(C, D) assert Mul(D, C) == MatMul(D, C)
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)