def _rebuild(expr): if not isinstance(expr, Basic): return expr if not expr.args: return expr if iterable(expr): new_args = [_rebuild(arg) for arg in expr] return expr.func(*new_args) if expr in subs: return subs[expr] orig_expr = expr if expr in opt_subs: expr = opt_subs[expr] # If enabled, parse Muls and Adds arguments by order to ensure # replacement order independent from hashes if order != 'none': if isinstance(expr, (Mul, MatMul)): c, nc = expr.args_cnc() if c == [1]: args = nc else: args = list(ordered(c)) + nc elif isinstance(expr, (Add, MatAdd)): args = list(ordered(expr.args)) else: args = expr.args else: args = expr.args new_args = list(map(_rebuild, args)) if new_args != args: new_expr = expr.func(*new_args) else: new_expr = expr if orig_expr in to_eliminate: try: sym = next(symbols) except StopIteration: raise ValueError("Symbols iterator ran out of symbols.") if isinstance(orig_expr, MatrixExpr): sym = MatrixSymbol(sym.name, orig_expr.rows, orig_expr.cols) subs[orig_expr] = sym replacements.append((sym, new_expr)) return sym else: return new_expr
def _linear_neq_order1_type3(match_): r""" System of n first-order nonconstant-coefficient linear homogeneous differential equations .. math:: X' = A(t) X where $X$ is the vector of $n$ dependent variables, $t$ is the dependent variable, $X'$ is the first order differential of $X$ with respect to $t$ and $A(t)$ is a $n \times n$ coefficient matrix. Let us define $B$ as antiderivative of coefficient matrix $A$: .. math:: B(t) = \int A(t) dt If the system of ODEs defined above is such that its antiderivative $B(t)$ commutes with $A(t)$ itself, then, the solution of the above system is given as: .. math:: X = \exp(B(t)) C where $C$ is the vector of constants. """ # Some parts of code is repeated, this needs to be taken care of # The constant vector obtained here can be done so in the match # function itself. eq = match_['eq'] func = match_['func'] fc = match_['func_coeff'] n = len(eq) t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] constants = numbered_symbols(prefix='C', cls=Symbol, start=1) # This needs to be modified in future so that fc is only of type Matrix M = -fc if type(fc) is Matrix else Matrix(n, n, lambda i,j:-fc[i,func[j],0]) Cvect = Matrix(list(next(constants) for _ in range(n))) # The code in if block will be removed when it is made sure # that the code works without the statements in if block. if "commutative_antiderivative" not in match_: B, is_commuting = _is_commutative_anti_derivative(M, t) # This course is subject to change if not is_commuting: return None else: B = match_['commutative_antiderivative'] sol_vector = B.exp() * Cvect # The expand_mul is added to handle the solutions so that # the exponential terms are collected properly. sol_vector = [collect(expand_mul(s), ordered(s.atoms(exp)), exact=True) for s in sol_vector] sol_dict = [Eq(func[i], sol_vector[i]) for i in range(n)] return sol_dict
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)
def _eval_simplify(self, ratio, measure, rational, inverse): args = [ a._eval_simplify(ratio, measure, rational, inverse) for a in self.args ] for i, (expr, cond) in enumerate(args): # try to simplify conditions and the expression for # equalities that are part of the condition, e.g. # Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) # -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): expr = expr.subs(*e.args) eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] other = [ei.subs(*e.args) for ei in other] cond = And(*(eqs + other)) args[i] = args[i].func(expr, cond) return self.func(*args)
def _eval_simplify(self, ratio, measure, rational, inverse): args = [a._eval_simplify(ratio, measure, rational, inverse) for a in self.args] for i, (expr, cond) in enumerate(args): # try to simplify conditions and the expression for # equalities that are part of the condition, e.g. # Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) # -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): expr = expr.subs(*e.args) eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] other = [ei.subs(*e.args) for ei in other] cond = And(*(eqs + other)) args[i] = args[i].func(expr, cond) return self.func(*args)
def _rebuild(expr): if expr.is_Atom: return expr if iterable(expr): new_args = [_rebuild(arg) for arg in expr] return expr.func(*new_args) if expr in subs: return subs[expr] orig_expr = expr if expr in opt_subs: expr = opt_subs[expr] # If enabled, parse Muls and Adds arguments by order to ensure # replacement order independent from hashes if order != 'none': if expr.is_Mul: c, nc = expr.args_cnc() args = list(ordered(c)) + nc elif expr.is_Add: args = list(ordered(expr.args)) else: args = expr.args else: args = expr.args new_args = list(map(_rebuild, args)) if new_args != args: new_expr = expr.func(*new_args) else: new_expr = expr if orig_expr in to_eliminate: try: sym = next(symbols) except StopIteration: raise ValueError("Symbols iterator ran out of symbols.") subs[orig_expr] = sym replacements.append((sym, new_expr)) return sym else: return new_expr
def _rebuild(expr): if not expr.args: return expr if iterable(expr): new_args = [_rebuild(arg) for arg in expr] return expr.func(*new_args) if expr in subs: return subs[expr] orig_expr = expr if expr in opt_subs: expr = opt_subs[expr] # If enabled, parse Muls and Adds arguments by order to ensure # replacement order independent from hashes if order != 'none': if expr.is_Mul: c, nc = expr.args_cnc() args = list(ordered(c)) + nc elif expr.is_Add: args = list(ordered(expr.args)) else: args = expr.args else: args = expr.args new_args = list(map(_rebuild, args)) if new_args != args: new_expr = expr.func(*new_args) else: new_expr = expr if orig_expr in to_eliminate: try: sym = next(symbols) except StopIteration: raise ValueError("Symbols iterator ran out of symbols.") subs[orig_expr] = sym replacements.append((sym, new_expr)) return sym else: return new_expr
def identities(expr, **hints): """ Apply to expr the identities specified in **hints. In the following, w is scalar, wa/wb are vectors. Hints: set a flag to True to apply the identity. By default all flags are set to False. prod_div = True nabla & (w * wa) = (Grad(w) & wa) + (w * (nabla & wa)) prod_curl = True nabla ^ (w * wa) = (Grad(w) ^ wa) + (w * (nabla ^ wa)) div_of_cross = True nabla & (wa ^ wb) = ((nabla ^ wa) & wb) - ((nabla ^ wb) & wa) curl_of_cross = True nabla ^ (wa ^ wb) = ((nabla & wb) * wa) + Advection(wb, wa) - ((nabla & wa) * wb) - Advection(wa, wb) grad_of_dot = True Grad(wa & wb) = Advection(wa, wb) + Advection(wb, wa) + (wa ^ (nabla ^ wb)) + (wb ^ (nabla ^ wa)) curl_of_grad = True nabla ^ Grad(w) = VectorZero() div_of_curl = True nabla & (nabla ^ wa) = S.Zero curl_of_curl = True nabla ^ (nabla ^ wa) = Grad(nabla & wa) - Laplace(wa) """ bac_cab = hints.get('bac_cab', False) if bac_cab: expr = bac_cab_backward(expr) for hint, val in hints.items(): if hint in _id.keys() and (val == True): pattern, subs = _id[hint] found = list(ordered(list(expr.find(pattern)))) print("found list", found) for i, f in enumerate(found): # proceed only if there is a match # Say we are in the previous iteration, i. Then we substitute # the previous match into found[i+1]. Now we are into iteration # i+1, but because we substituted a previous result into it, it # might not be "a pattern" anymore. Need to check it! m = f.match(pattern) print("\tf", f, m) if m: fsubs = subs.xreplace(m) # update found list for j in range(i + 1, len(found)): found[j] = found[j].subs(f, fsubs) expr = expr.subs(f, fsubs) return expr
def find_double_cross(expr): """ Given a vector expression, return a list of elements satisfying the pattern A x (B x C), where A, B, C are vectors and `x` is the cross product. The list is ordered in such a way that nested matches comes first (similarly to scanning the expression tree from bottom to top). """ A, B, C = [WVS(t) for t in ["A", "B", "C"]] pattern = A ^ (B ^ C) return list(ordered(list(expr.find(pattern))))
def _collect_coeff_from_product(expr, pattern): """ Collect the coefficients of nested dot (or cross) products. For example: (x * A) ^ ((y * B) ^ (z * C)) = (x * y * z) * A ^ (B ^ C) """ found = list(ordered(list(expr.find(pattern)))) for i, f in enumerate(found): newf = _as_coeff_product(f) for j in range(i + 1, len(found)): found[j] = found[j].xreplace({f: newf}) expr = expr.subs({f: newf}) return expr
def test_ordered(): assert list(ordered((x, y), hash, default=False)) in [[x, y], [y, x]] assert list(ordered((x, y), hash, default=False)) == list(ordered((y, x), hash, default=False)) assert list(ordered((x, y))) == [x, y] seq, keys = [[[1, 2, 1], [0, 3, 1], [1, 1, 3], [2], [1]], (lambda x: len(x), lambda x: sum(x))] assert list(ordered(seq, keys, default=False, warn=False)) == [[1], [2], [1, 2, 1], [0, 3, 1], [1, 1, 3]] raises(ValueError, lambda: list(ordered(seq, keys, default=False, warn=True)))
def func(expr, pattern, prev=None): # debug("\n", hints) old = expr found = list(ordered(list(expr.find(pattern)))) # print("func", expr) # debug("found", found) # print("found", found) for f in found: fexp = f.expand(**hints) # debug("\t fexp", fexp) if f != fexp: expr = expr.xreplace({f: fexp}) # debug("expr", expr) if old != expr: expr = func(expr, pattern) return expr
def test_ordered(): assert list(ordered((x, y), hash, default=False)) in [[x, y], [y, x]] assert list(ordered((x, y), hash, default=False)) == \ list(ordered((y, x), hash, default=False)) assert list(ordered((x, y))) == [x, y] seq, keys = [[[1, 2, 1], [0, 3, 1], [1, 1, 3], [2], [1]], (lambda x: len(x), lambda x: sum(x))] assert list(ordered(seq, keys, default=False, warn=False)) == \ [[1], [2], [1, 2, 1], [0, 3, 1], [1, 1, 3]] raises(ValueError, lambda: list(ordered(seq, keys, default=False, warn=True)))
def get_lambda(expr, **kwargs): """Create a lambda function to numerically evaluate expr by sorting alphabetically the function arguments. Parameters ---------- expr : Expr The Sympy expression to convert. **kwargs Other keyword arguments to the function lambdify. Returns ------- s : list The function signature: a list of the ordered function arguments. f : lambda The generated lambda function.ò """ signature = list(ordered(expr.free_symbols)) return signature, lambdify(signature, expr, **kwargs)
def _linear_neq_order1_type1(match_): r""" System of n first-order constant-coefficient linear homogeneous differential equations .. math:: y'_k = a_{k1} y_1 + a_{k2} y_2 +...+ a_{kn} y_n; k = 1,2,...,n or that can be written as `\vec{y'} = A . \vec{y}` where `\vec{y}` is matrix of `y_k` for `k = 1,2,...n` and `A` is a `n \times n` matrix. These equations are equivalent to a first order homogeneous linear differential equation. The system of ODEs described above has a unique solution, namely: .. math :: \vec{y} = \exp(A t) C where $t$ is the independent variable and $C$ is a vector of n constants. These are constants from the integration. """ eq = match_['eq'] func = match_['func'] fc = match_['func_coeff'] n = len(eq) t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] constants = numbered_symbols(prefix='C', cls=Symbol, start=1) # This needs to be modified in future so that fc is only of type Matrix M = -fc if type(fc) is Matrix else Matrix(n, n, lambda i, j: -fc[i, func[j], 0]) P, J = matrix_exp_jordan_form(M, t) P = simplify(P) Cvect = Matrix(list(next(constants) for _ in range(n))) sol_vector = P * (J * Cvect) gens = sol_vector.atoms(exp) sol_vector = [collect(s, ordered(gens), exact=True) for s in sol_vector] sol_dict = [Eq(func[i], sol_vector[i]) for i in range(n)] return sol_dict
def _find_and_replace(expr, pattern, rep, matches=None): """ Given an expression, a search `pattern` and a replacement pattern `rep`, search for `pattern` in the expression and substitute it with `rep`. If matches is given, skip the pattern search and substitute each match with the corresponding `rep` pattern. """ if not matches: # list of matches ordered accordingly to the length of the match. # The shorter the match expression, the lower it should be in the # expression tree. Substitute from the bottom up! found = list(ordered(list(expr.find(pattern)))) if len(found) > 0: f = found[0] expr = expr.xreplace({f: rep.xreplace(f.match(pattern))}) # repeat the procedure with the updated expression expr = _find_and_replace(expr, pattern, rep) return expr if not isinstance(matches, (list, tuple)): matches = [matches] for match in matches: expr = expr.xreplace({match: rep.xreplace(match.match(pattern))}) return expr
def get_lambda(expr, modules=["numpy", "scipy"], **kwargs): """ Create a lambda function to numerically evaluate expr by sorting alphabetically the function arguments. Parameters ---------- expr : Expr The Sympy expression to convert. modules : str The numerical module to use for evaluation. Default to ["numpy", "scipy"]. See help(lambdify) for other choices. **kwargs Other keyword arguments to the function lambdify. Returns ------- s : list The function signature: a list of the ordered function arguments. f : lambda The generated lambda function.ò """ from sympy.utilities.iterables import ordered signature = list(ordered(expr.free_symbols)) return signature, lambdify(signature, expr, modules=modules, **kwargs)
def _intervals(self, sym): """Return a list of unique tuples, (a, b, e, i), where a and b are the lower and upper bounds in which the expression e of argument i in self is defined and a < b (when involving numbers) or a <= b when involving symbols. If there are any relationals not involving sym, or any relational cannot be solved for sym, NotImplementedError is raised. The calling routine should have removed such relationals before calling this routine. The evaluated conditions will be returned as ranges. Discontinuous ranges will be returned separately with identical expressions. The first condition that evaluates to True will be returned as the last tuple with a, b = -oo, oo. """ from sympy.solvers.inequalities import _solve_inequality from sympy.logic.boolalg import to_cnf, distribute_or_over_and assert isinstance(self, Piecewise) def _solve_relational(r): if sym not in r.free_symbols: nonsymfail(r) rv = _solve_inequality(r, sym) if isinstance(rv, Relational): free = rv.args[1].free_symbols if rv.args[0] != sym or sym in free: raise NotImplementedError(filldedent(''' Unable to solve relational %s for %s.''' % (r, sym))) if rv.rel_op == '==': # this equality has been affirmed to have the form # Eq(sym, rhs) where rhs is sym-free; it represents # a zero-width interval which will be ignored # whether it is an isolated condition or contained # within an And or an Or rv = S.false elif rv.rel_op == '!=': try: rv = Or(sym < rv.rhs, sym > rv.rhs) except TypeError: # e.g. x != I ==> all real x satisfy rv = S.true elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): rv = S.true return rv def nonsymfail(cond): raise NotImplementedError(filldedent(''' A condition not involving %s appeared: %s''' % (sym, cond))) # make self canonical wrt Relationals reps = { r: _solve_relational(r) for r in self.atoms(Relational)} # process args individually so if any evaluate, their position # in the original Piecewise will be known args = [i.xreplace(reps) for i in self.args] # precondition args expr_cond = [] default = idefault = None for i, (expr, cond) in enumerate(args): if cond is S.false: continue elif cond is S.true: default = expr idefault = i break cond = to_cnf(cond) if isinstance(cond, And): cond = distribute_or_over_and(cond) if isinstance(cond, Or): expr_cond.extend( [(i, expr, o) for o in cond.args if not isinstance(o, Equality)]) elif cond is not S.false: expr_cond.append((i, expr, cond)) # determine intervals represented by conditions int_expr = [] for iarg, expr, cond in expr_cond: if isinstance(cond, And): lower = S.NegativeInfinity upper = S.Infinity exclude = [] for cond2 in cond.args: if isinstance(cond2, Equality): lower = upper # ignore break elif isinstance(cond2, Unequality): l, r = cond2.args if l == sym: exclude.append(r) elif r == sym: exclude.append(l) else: nonsymfail(cond2) continue elif cond2.lts == sym: upper = Min(cond2.gts, upper) elif cond2.gts == sym: lower = Max(cond2.lts, lower) else: nonsymfail(cond2) # should never get here if exclude: exclude = list(ordered(exclude)) newcond = [] for i, e in enumerate(exclude): if e < lower == True or e > upper == True: continue if not newcond: newcond.append((None, lower)) # add a primer newcond.append((newcond[-1][1], e)) newcond.append((newcond[-1][1], upper)) newcond.pop(0) # remove the primer expr_cond.extend([(iarg, expr, And(i[0] < sym, sym < i[1])) for i in newcond]) continue elif isinstance(cond, Relational): lower, upper = cond.lts, cond.gts # part 1: initialize with givens if cond.lts == sym: # part 1a: expand the side ... lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 elif cond.gts == sym: # part 1a: ... that can be expanded upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 else: nonsymfail(cond) else: raise NotImplementedError( 'unrecognized condition: %s' % cond) lower, upper = lower, Max(lower, upper) if (lower >= upper) is not S.true: int_expr.append((lower, upper, expr, iarg)) if default is not None: int_expr.append( (S.NegativeInfinity, S.Infinity, default, idefault)) return list(uniq(int_expr))
def _handle_irel(self, x, handler): """Return either None (if the conditions of self depend only on x) else a Piecewise expression whose expressions (handled by the handler that was passed) are paired with the governing x-independent relationals, e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) -> Piecewise( (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)), (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True)) """ # identify governing relationals rel = self.atoms(Relational) irel = list(ordered([r for r in rel if x not in r.free_symbols and r not in (S.true, S.false)])) if irel: args = {} exprinorder = [] for truth in product((1, 0), repeat=len(irel)): reps = dict(zip(irel, truth)) # only store the true conditions since the false are implied # when they appear lower in the Piecewise args if 1 not in truth: cond = None # flag this one so it doesn't get combined else: andargs = Tuple(*[i for i in reps if reps[i]]) free = list(andargs.free_symbols) if len(free) == 1: from sympy.solvers.inequalities import ( reduce_inequalities, _solve_inequality) try: t = reduce_inequalities(andargs, free[0]) # ValueError when there are potentially # nonvanishing imaginary parts except (ValueError, NotImplementedError): # at least isolate free symbol on left t = And(*[_solve_inequality( a, free[0], linear=True) for a in andargs]) else: t = And(*andargs) if t is S.false: continue # an impossible combination cond = t expr = handler(self.xreplace(reps)) if isinstance(expr, self.func) and len(expr.args) == 1: expr, econd = expr.args[0] cond = And(econd, True if cond is None else cond) # the ec pairs are being collected since all possibilities # are being enumerated, but don't put the last one in since # its expr might match a previous expression and it # must appear last in the args if cond is not None: args.setdefault(expr, []).append(cond) # but since we only store the true conditions we must maintain # the order so that the expression with the most true values # comes first exprinorder.append(expr) # convert collected conditions as args of Or for k in args: args[k] = Or(*args[k]) # take them in the order obtained args = [(e, args[e]) for e in uniq(exprinorder)] # add in the last arg args.append((expr, True)) # if any condition reduced to True, it needs to go last # and there should only be one of them or else the exprs # should agree trues = [i for i in range(len(args)) if args[i][1] is S.true] if not trues: # make the last one True since all cases were enumerated e, c = args[-1] args[-1] = (e, S.true) else: assert len(set([e for e, c in [args[i] for i in trues]])) == 1 args.append(args.pop(trues.pop())) while trues: args.pop(trues.pop()) return Piecewise(*args)
def _collapse_arguments(cls, args, **assumptions): """Remove redundant args. Examples ======== >>> from sympy import Min, Max >>> from sympy.abc import a, b, c, d, e Any arg in parent that appears in any parent-like function in any of the flat args of parent can be removed from that sub-arg: >>> Min(a, Max(b, Min(a, c, d))) Min(a, Max(b, Min(c, d))) If the arg of parent appears in an opposite-than parent function in any of the flat args of parent that function can be replaced with the arg: >>> Min(a, Max(b, Min(c, d, Max(a, e)))) Min(a, Max(b, Min(a, c, d))) """ from sympy.utilities.iterables import ordered from sympy.simplify.simplify import walk if not args: return args args = list(ordered(args)) if cls == Min: other = Max else: other = Min # find global comparable max of Max and min of Min if a new # value is being introduced in these args at position 0 of # the ordered args if args[0].is_number: sifted = mins, maxs = [], [] for i in args: for v in walk(i, Min, Max): if v.args[0].is_comparable: sifted[isinstance(v, Max)].append(v) small = Min.identity for i in mins: v = i.args[0] if v.is_number and (v < small) == True: small = v big = Max.identity for i in maxs: v = i.args[0] if v.is_number and (v > big) == True: big = v # at the point when this function is called from __new__, # there may be more than one numeric arg present since # local zeros have not been handled yet, so look through # more than the first arg if cls == Min: for i in range(len(args)): if not args[i].is_number: break if (args[i] < small) == True: small = args[i] elif cls == Max: for i in range(len(args)): if not args[i].is_number: break if (args[i] > big) == True: big = args[i] T = None if cls == Min: if small != Min.identity: other = Max T = small elif big != Max.identity: other = Min T = big if T is not None: # remove numerical redundancy for i in range(len(args)): a = args[i] if isinstance(a, other): a0 = a.args[0] if ((a0 > T) if other == Max else (a0 < T)) == True: args[i] = cls.identity # remove redundant symbolic args def do(ai, a): if not isinstance(ai, (Min, Max)): return ai cond = a in ai.args if not cond: return ai.func(*[do(i, a) for i in ai.args], evaluate=False) if isinstance(ai, cls): return ai.func(*[do(i, a) for i in ai.args if i != a], evaluate=False) return a for i, a in enumerate(args): args[i + 1:] = [do(ai, a) for ai in args[i + 1:]] # factor out common elements as for # Min(Max(x, y), Max(x, z)) -> Max(x, Min(y, z)) # and vice versa when swapping Min/Max -- do this only for the # easy case where all functions contain something in common; # trying to find some optimal subset of args to modify takes # too long if len(args) > 1: common = None remove = [] sets = [] for i in range(len(args)): a = args[i] if not isinstance(a, other): continue s = set(a.args) common = s if common is None else (common & s) if not common: break sets.append(s) remove.append(i) if common: sets = filter(None, [s - common for s in sets]) sets = [other(*s, evaluate=False) for s in sets] for i in reversed(remove): args.pop(i) oargs = [cls(*sets)] if sets else [] oargs.extend(common) args.append(other(*oargs, evaluate=False)) return args
def _handle_irel(self, x, handler): """Return either None (if the conditions of self depend only on x) else a Piecewise expression whose expressions (handled by the handler that was passed) are paired with the governing x-independent relationals, e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) -> Piecewise( (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)), (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True)) """ # identify governing relationals rel = self.atoms(Relational) irel = list( ordered([ r for r in rel if x not in r.free_symbols and r not in (S.true, S.false) ])) if irel: args = {} exprinorder = [] for truth in product((1, 0), repeat=len(irel)): reps = dict(zip(irel, truth)) # only store the true conditions since the false are implied # when they appear lower in the Piecewise args if 1 not in truth: cond = None # flag this one so it doesn't get combined else: andargs = Tuple(*[i for i in reps if reps[i]]) free = list(andargs.free_symbols) if len(free) == 1: from sympy.solvers.inequalities import ( reduce_inequalities, _solve_inequality) try: t = reduce_inequalities(andargs, free[0]) # ValueError when there are potentially # nonvanishing imaginary parts except (ValueError, NotImplementedError): # at least isolate free symbol on left t = And(*[ _solve_inequality(a, free[0], linear=True) for a in andargs ]) else: t = And(*andargs) if t is S.false: continue # an impossible combination cond = t expr = handler(self.xreplace(reps)) if isinstance(expr, self.func) and len(expr.args) == 1: expr, econd = expr.args[0] cond = And(econd, True if cond is None else cond) # the ec pairs are being collected since all possibilities # are being enumerated, but don't put the last one in since # its expr might match a previous expression and it # must appear last in the args if cond is not None: args.setdefault(expr, []).append(cond) # but since we only store the true conditions we must maintain # the order so that the expression with the most true values # comes first exprinorder.append(expr) # convert collected conditions as args of Or for k in args: args[k] = Or(*args[k]) # take them in the order obtained args = [(e, args[e]) for e in uniq(exprinorder)] # add in the last arg args.append((expr, True)) # if any condition reduced to True, it needs to go last # and there should only be one of them or else the exprs # should agree trues = [i for i in range(len(args)) if args[i][1] is S.true] if not trues: # make the last one True since all cases were enumerated e, c = args[-1] args[-1] = (e, S.true) else: assert len(set([e for e, c in [args[i] for i in trues]])) == 1 args.append(args.pop(trues.pop())) while trues: args.pop(trues.pop()) return Piecewise(*args)
def piecewise_fold(expr): """ Takes an expression containing a piecewise function and returns the expression in piecewise form. In addition, any ITE conditions are rewritten in negation normal form and simplified. Examples ======== >>> from sympy import Piecewise, piecewise_fold, sympify as S >>> from sympy.abc import x >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) >>> piecewise_fold(x*p) Piecewise((x**2, x < 1), (x, True)) See Also ======== Piecewise """ if not isinstance(expr, Basic) or not expr.has(Piecewise): return expr new_args = [] if isinstance(expr, (ExprCondPair, Piecewise)): for e, c in expr.args: if not isinstance(e, Piecewise): e = piecewise_fold(e) # we don't keep Piecewise in condition because # it has to be checked to see that it's complete # and we convert it to ITE at that time assert not c.has(Piecewise) # pragma: no cover if isinstance(c, ITE): c = c.to_nnf() c = simplify_logic(c, form='cnf') if isinstance(e, Piecewise): new_args.extend([(piecewise_fold(ei), And(ci, c)) for ei, ci in e.args]) else: new_args.append((e, c)) else: from sympy.utilities.iterables import cartes, sift, common_prefix # Given # P1 = Piecewise((e11, c1), (e12, c2), A) # P2 = Piecewise((e21, c1), (e22, c2), B) # ... # the folding of f(P1, P2) is trivially # Piecewise( # (f(e11, e21), c1), # (f(e12, e22), c2), # (f(Piecewise(A), Piecewise(B)), True)) # Certain objects end up rewriting themselves as thus, so # we do that grouping before the more generic folding. # The following applies this idea when f = Add or f = Mul # (and the expression is commutative). if expr.is_Add or expr.is_Mul and expr.is_commutative: p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) pc = sift(p, lambda x: tuple([c for e, c in x.args])) for c in list(ordered(pc)): if len(pc[c]) > 1: pargs = [list(i.args) for i in pc[c]] # the first one is the same; there may be more com = common_prefix(*[[i.cond for i in j] for j in pargs]) n = len(com) collected = [] for i in range(n): collected.append( (expr.func(*[ai[i].expr for ai in pargs]), com[i])) remains = [] for a in pargs: if n == len(a): # no more args continue if a[n].cond == True: # no longer Piecewise remains.append(a[n].expr) else: # restore the remaining Piecewise remains.append(Piecewise(*a[n:], evaluate=False)) if remains: collected.append((expr.func(*remains), True)) args.append(Piecewise(*collected, evaluate=False)) continue args.extend(pc[c]) else: args = expr.args # fold folded = list(map(piecewise_fold, args)) for ec in cartes(*[(i.args if isinstance(i, Piecewise) else [(i, true)]) for i in folded]): e, c = zip(*ec) new_args.append((expr.func(*e), And(*c))) return Piecewise(*new_args)
def __hash__(self): # Factors keys = tuple(ordered(self.factors.keys())) values = [self.factors[k] for k in keys] return hash((keys, values))
def __repr__(self): # Factors return "Factors({%s})" % ', '.join( ['%s: %s' % (k, v) for k, v in ordered(self.factors.items())])
def piecewise_fold(expr): """ Takes an expression containing a piecewise function and returns the expression in piecewise form. In addition, any ITE conditions are rewritten in negation normal form and simplified. Examples ======== >>> from sympy import Piecewise, piecewise_fold, sympify as S >>> from sympy.abc import x >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) >>> piecewise_fold(x*p) Piecewise((x**2, x < 1), (x, True)) See Also ======== Piecewise """ if not isinstance(expr, Basic) or not expr.has(Piecewise): return expr new_args = [] if isinstance(expr, (ExprCondPair, Piecewise)): for e, c in expr.args: if not isinstance(e, Piecewise): e = piecewise_fold(e) # we don't keep Piecewise in condition because # it has to be checked to see that it's complete # and we convert it to ITE at that time assert not c.has(Piecewise) # pragma: no cover if isinstance(c, ITE): c = c.to_nnf() c = simplify_logic(c, form='cnf') if isinstance(e, Piecewise): new_args.extend([(piecewise_fold(ei), And(ci, c)) for ei, ci in e.args]) else: new_args.append((e, c)) else: from sympy.utilities.iterables import cartes, sift, common_prefix # Given # P1 = Piecewise((e11, c1), (e12, c2), A) # P2 = Piecewise((e21, c1), (e22, c2), B) # ... # the folding of f(P1, P2) is trivially # Piecewise( # (f(e11, e21), c1), # (f(e12, e22), c2), # (f(Piecewise(A), Piecewise(B)), True)) # Certain objects end up rewriting themselves as thus, so # we do that grouping before the more generic folding. # The following applies this idea when f = Add or f = Mul # (and the expression is commutative). if expr.is_Add or expr.is_Mul and expr.is_commutative: p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) pc = sift(p, lambda x: tuple([c for e,c in x.args])) for c in list(ordered(pc)): if len(pc[c]) > 1: pargs = [list(i.args) for i in pc[c]] # the first one is the same; there may be more com = common_prefix(*[ [i.cond for i in j] for j in pargs]) n = len(com) collected = [] for i in range(n): collected.append(( expr.func(*[ai[i].expr for ai in pargs]), com[i])) remains = [] for a in pargs: if n == len(a): # no more args continue if a[n].cond == True: # no longer Piecewise remains.append(a[n].expr) else: # restore the remaining Piecewise remains.append( Piecewise(*a[n:], evaluate=False)) if remains: collected.append((expr.func(*remains), True)) args.append(Piecewise(*collected, evaluate=False)) continue args.extend(pc[c]) else: args = expr.args # fold folded = list(map(piecewise_fold, args)) for ec in cartes(*[ (i.args if isinstance(i, Piecewise) else [(i, true)]) for i in folded]): e, c = zip(*ec) new_args.append((expr.func(*e), And(*c))) return Piecewise(*new_args)
def _eval_simplify(self, ratio, measure, rational, inverse): args = [a._eval_simplify(ratio, measure, rational, inverse) for a in self.args] for i, (expr, cond) in enumerate(args): # try to simplify conditions and the expression for # equalities that are part of the condition, e.g. # Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) # -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): expr = expr.subs(*e.args) eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] other = [ei.subs(*e.args) for ei in other] cond = And(*(eqs + other)) args[i] = args[i].func(expr, cond) # See if expressions valid for a single point happens to evaluate # to the same function as in the next piecewise segment, see: # https://github.com/sympy/sympy/issues/8458 prevexpr = None for i, (expr, cond) in reversed(list(enumerate(args))): if prevexpr is not None: _prevexpr = prevexpr _expr = expr if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): _prevexpr = _prevexpr.subs(*e.args) _expr = _expr.subs(*e.args) if _prevexpr == _expr: args[i] = args[i].func(args[i+1][0], cond) else: prevexpr = expr else: prevexpr = expr return self.func(*args)
def tree_cse(exprs, symbols, opt_subs=None, order='canonical', ignore=()): """Perform raw CSE on expression tree, taking opt_subs into account. Parameters ========== exprs : list of sympy expressions The expressions to reduce. symbols : infinite iterator yielding unique Symbols The symbols used to label the common subexpressions which are pulled out. opt_subs : dictionary of expression substitutions The expressions to be substituted before any CSE action is performed. 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'. ignore : iterable of Symbols Substitutions containing any Symbol from ``ignore`` will be ignored. """ from sympy.matrices.expressions import MatrixExpr, MatrixSymbol, MatMul, MatAdd if opt_subs is None: opt_subs = dict() ## Find repeated sub-expressions to_eliminate = set() seen_subexp = set() def _find_repeated(expr): if not isinstance(expr, Basic): return if expr.is_Atom or expr.is_Order: return if iterable(expr): args = expr else: if expr in seen_subexp: for ign in ignore: if ign in expr.free_symbols: break else: to_eliminate.add(expr) return seen_subexp.add(expr) if expr in opt_subs: expr = opt_subs[expr] args = expr.args list(map(_find_repeated, args)) for e in exprs: if isinstance(e, Basic): _find_repeated(e) ## Rebuild tree replacements = [] subs = dict() def _rebuild(expr): if not isinstance(expr, Basic): return expr if not expr.args: return expr if iterable(expr): new_args = [_rebuild(arg) for arg in expr] return expr.func(*new_args) if expr in subs: return subs[expr] orig_expr = expr if expr in opt_subs: expr = opt_subs[expr] # If enabled, parse Muls and Adds arguments by order to ensure # replacement order independent from hashes if order != 'none': if isinstance(expr, (Mul, MatMul)): c, nc = expr.args_cnc() if c == [1]: args = nc else: args = list(ordered(c)) + nc elif isinstance(expr, (Add, MatAdd)): args = list(ordered(expr.args)) else: args = expr.args else: args = expr.args new_args = list(map(_rebuild, args)) if new_args != args: new_expr = expr.func(*new_args) else: new_expr = expr if orig_expr in to_eliminate: try: sym = next(symbols) except StopIteration: raise ValueError("Symbols iterator ran out of symbols.") if isinstance(orig_expr, MatrixExpr): sym = MatrixSymbol(sym.name, orig_expr.rows, orig_expr.cols) subs[orig_expr] = sym replacements.append((sym, new_expr)) return sym else: return new_expr reduced_exprs = [] for e in exprs: if isinstance(e, Basic): reduced_e = _rebuild(e) else: reduced_e = e reduced_exprs.append(reduced_e) # don't allow hollow nesting; restore expressions that were # subject to "advantageous grouping" # e.g if p = [b + 2*d + e + f, b + 2*d + f + g, a + c + d + f + g] # and R, C = cse(p) then # R = [(x0, d + f), (x1, b + d)] # C = [e + x0 + x1, g + x0 + x1, a + c + d + f + g] # but the args of C[-1] should not be `(a + c, d + f + g)` subs_opt = list(ordered([(v, k) for k, v in opt_subs.items()])) for i, e in enumerate(reduced_exprs): reduced_exprs[i] = e.subs(subs_opt) return replacements, reduced_exprs
def _match_common_args(Func, funcs): if order != 'none': funcs = list(ordered(funcs)) else: funcs = sorted(funcs, key=lambda x: len(x.args)) if Func is Mul: F = Pow meth = 'as_powers_dict' from sympy.core.add import _addsort as inplace_sorter elif Func is Add: F = Mul meth = 'as_coefficients_dict' from sympy.core.mul import _mulsort as inplace_sorter else: assert None # expected Mul or Add # ----------------- helpers --------------------------- def ufunc(*args): # return a well formed unevaluated function from the args # SHARES Func, inplace_sorter args = list(args) inplace_sorter(args) return Func(*args, evaluate=False) def as_dict(e): # creates a dictionary of the expression using either # as_coefficients_dict or as_powers_dict, depending on Func # SHARES meth d = getattr(e, meth, lambda: {a: S.One for a in e.args})() for k in list(d.keys()): try: as_int(d[k]) except ValueError: d[F(k, d.pop(k))] = S.One return d def from_dict(d): # build expression from dict from # as_coefficients_dict or as_powers_dict # SHARES F return ufunc(*[F(k, v) for k, v in d.items()]) def update(k): # updates all of the info associated with k using # the com_dict: func_dicts, func_args, opt_subs # returns True if all values were updated, else None # SHARES com_dict, com_func, func_dicts, func_args, # opt_subs, funcs, verbose for di in com_dict: # don't allow a sign to change if com_dict[di] > func_dicts[k][di]: return # remove it if Func is Add: take = min(func_dicts[k][i] for i in com_dict) com_func_take = Mul(take, from_dict(com_dict), evaluate=False) else: take = igcd(*[func_dicts[k][i] for i in com_dict]) com_func_take = Pow(from_dict(com_dict), take, evaluate=False) for di in com_dict: func_dicts[k][di] -= take*com_dict[di] # compute the remaining expression rem = from_dict(func_dicts[k]) # reject hollow change, e.g extracting x + 1 from x + 3 if Func is Add and rem and rem.is_Integer and 1 in com_dict: return if verbose: print('\nfunc %s (%s) \ncontains %s \nas %s \nleaving %s' % (funcs[k], func_dicts[k], com_func, com_func_take, rem)) # recompute the dict since some keys may now # have corresponding values of 0; one could # keep track of which ones went to zero but # this seems cleaner func_dicts[k] = as_dict(rem) # update associated info func_dicts[k][com_func] = take func_args[k] = set(func_dicts[k]) # keep the constant separate from the remaining # part of the expression, e.g. 2*(a*b) rather than 2*a*b opt_subs[funcs[k]] = ufunc(rem, com_func_take) # everything was updated return True def get_copy(i): return [func_dicts[i].copy(), func_args[i].copy(), funcs[i], i] def restore(dafi): i = dafi.pop() func_dicts[i], func_args[i], funcs[i] = dafi # ----------------- end helpers ----------------------- func_dicts = [as_dict(f) for f in funcs] func_args = [set(d) for d in func_dicts] while True: hit = pairwise_most_common(func_args) if not hit or len(hit[0][0]) <= 1: break changed = False for com_args, ij in hit: take = len(com_args) ALL = list(ordered(com_args)) while take >= 2: for com_args in subsets(ALL, take): com_func = Func(*com_args) com_dict = as_dict(com_func) for i, j in ij: dafi = None if com_func != funcs[i]: dafi = get_copy(i) ch = update(i) if not ch: restore(dafi) continue if com_func != funcs[j]: dafj = get_copy(j) ch = update(j) if not ch: if dafi is not None: restore(dafi) restore(dafj) continue changed = True if changed: break else: take -= 1 continue break else: continue break if not changed: break
def cse(exprs, symbols=None, optimizations=None, postprocess=None): """ Perform common subexpression elimination on an expression. Parameters ========== exprs : list of sympy expressions, or a single sympy expression The expressions to reduce. symbols : infinite iterator yielding unique Symbols The symbols used to label the common subexpressions which are pulled out. The ``numbered_symbols`` generator is useful. The default is a stream of symbols of the form "x0", "x1", etc. This must be an infinite iterator. optimizations : list of (callable, callable) pairs, optional The (preprocessor, postprocessor) pairs. If not provided, ``sympy.simplify.cse.cse_optimizations`` is used. postprocess : a function which accepts the two return values of cse and returns the desired form of output from cse, e.g. if you want the replacements reversed the function might be the following lambda: lambda r, e: return reversed(r), e Returns ======= replacements : list of (Symbol, expression) pairs All of the common subexpressions that were replaced. Subexpressions earlier in this list might show up in subexpressions later in this list. reduced_exprs : list of sympy expressions The reduced expressions with all of the replacements above. """ from sympy.matrices import Matrix if symbols is None: symbols = numbered_symbols() else: # In case we get passed an iterable with an __iter__ method instead of # an actual iterator. symbols = iter(symbols) tmp_symbols = numbered_symbols('_csetmp') subexp_iv = dict() muls = set() adds = set() if optimizations is None: # Pull out the default here just in case there are some weird # manipulations of the module-level list in some other thread. optimizations = list(cse_optimizations) # Handle the case if just one expression was passed. if isinstance(exprs, Basic): exprs = [exprs] # Preprocess the expressions to give us better optimization opportunities. prep_exprs = [preprocess_for_cse(e, optimizations) for e in exprs] # Find all subexpressions. def _parse(expr): if expr.is_Atom: # Exclude atoms, since there is no point in renaming them. return expr if iterable(expr): return expr subexpr = type(expr)(*map(_parse, expr.args)) if subexpr in subexp_iv: return subexp_iv[subexpr] if subexpr.is_Mul: muls.add(subexpr) elif subexpr.is_Add: adds.add(subexpr) ivar = next(tmp_symbols) subexp_iv[subexpr] = ivar return ivar tmp_exprs = list() for expr in prep_exprs: if isinstance(expr, Basic): tmp_exprs.append(_parse(expr)) else: tmp_exprs.append(expr) # process adds - any adds that weren't repeated might contain # subpatterns that are repeated, e.g. x+y+z and x+y have x+y in common adds = list(ordered(adds)) addargs = [set(a.args) for a in adds] for i in xrange(len(addargs)): for j in xrange(i + 1, len(addargs)): com = addargs[i].intersection(addargs[j]) if len(com) > 1: add_subexp = Add(*com) diff_add_i = addargs[i].difference(com) diff_add_j = addargs[j].difference(com) if add_subexp in subexp_iv: ivar = subexp_iv[add_subexp] else: ivar = next(tmp_symbols) subexp_iv[add_subexp] = ivar if diff_add_i: newadd = Add(ivar,*diff_add_i) subexp_iv[newadd] = subexp_iv.pop(adds[i]) adds[i] = newadd #else add_i is itself subexp_iv[add_subexp] -> ivar if diff_add_j: newadd = Add(ivar,*diff_add_j) subexp_iv[newadd] = subexp_iv.pop(adds[j]) adds[j] = newadd #else add_j is itself subexp_iv[add_subexp] -> ivar addargs[i] = diff_add_i addargs[j] = diff_add_j for k in xrange(j + 1, len(addargs)): if com.issubset(addargs[k]): diff_add_k = addargs[k].difference(com) if diff_add_k: newadd = Add(ivar,*diff_add_k) subexp_iv[newadd] = subexp_iv.pop(adds[k]) adds[k] = newadd #else add_k is itself subexp_iv[add_subexp] -> ivar addargs[k] = diff_add_k # process muls - any muls that weren't repeated might contain # subpatterns that are repeated, e.g. x*y*z and x*y have x*y in common # *assumes that there are no non-commutative parts* muls = list(ordered(muls)) mulargs = [set(a.args) for a in muls] for i in xrange(len(mulargs)): for j in xrange(i + 1, len(mulargs)): com = mulargs[i].intersection(mulargs[j]) if len(com) > 1: mul_subexp = Mul(*com) diff_mul_i = mulargs[i].difference(com) diff_mul_j = mulargs[j].difference(com) if mul_subexp in subexp_iv: ivar = subexp_iv[mul_subexp] else: ivar = next(tmp_symbols) subexp_iv[mul_subexp] = ivar if diff_mul_i: newmul = Mul(ivar,*diff_mul_i) subexp_iv[newmul] = subexp_iv.pop(muls[i]) muls[i] = newmul #else mul_i is itself subexp_iv[mul_subexp] -> ivar if diff_mul_j: newmul = Mul(ivar,*diff_mul_j) subexp_iv[newmul] = subexp_iv.pop(muls[j]) muls[j] = newmul #else mul_j is itself subexp_iv[mul_subexp] -> ivar mulargs[i] = diff_mul_i mulargs[j] = diff_mul_j for k in xrange(j + 1, len(mulargs)): if com.issubset(mulargs[k]): diff_mul_k = mulargs[k].difference(com) if diff_mul_k: newmul = Mul(ivar,*diff_mul_k) subexp_iv[newmul] = subexp_iv.pop(muls[k]) muls[k] = newmul #else mul_k is itself subexp_iv[mul_subexp] -> ivar mulargs[k] = diff_mul_k # Find all of the repeated subexpressions. ivar_se = {iv:se for se,iv in subexp_iv.iteritems()} used_ivs = set() repeated = set() def _find_repeated_subexprs(subexpr): if subexpr.is_Atom: symbs = [subexpr] else: symbs = subexpr.args for symb in symbs: if symb in ivar_se: if symb not in used_ivs: _find_repeated_subexprs(ivar_se[symb]) used_ivs.add(symb) else: repeated.add(symb) for expr in tmp_exprs: _find_repeated_subexprs(expr) # Substitute symbols for all of the repeated subexpressions. # remove temporary replacements that weren't used more than once tmpivs_ivs = dict() ordered_iv_se = OrderedDict() def _get_subexprs(args): args = list(args) for i,symb in enumerate(args): if symb in ivar_se: if symb in tmpivs_ivs: args[i] = tmpivs_ivs[symb] else: subexpr = ivar_se[symb] subexpr = type(subexpr)(*_get_subexprs(subexpr.args)) if symb in repeated: ivar = next(symbols) ordered_iv_se[ivar] = subexpr tmpivs_ivs[symb] = ivar args[i] = ivar else: args[i] = subexpr return args out_exprs = _get_subexprs(tmp_exprs) # Postprocess the expressions to return the expressions to canonical form. ordered_iv_se_notopt = ordered_iv_se ordered_iv_se = OrderedDict() for i, (ivar, subexpr) in enumerate(ordered_iv_se_notopt.items()): subexpr = postprocess_for_cse(subexpr, optimizations) ordered_iv_se[ivar] = subexpr out_exprs = [postprocess_for_cse(e, optimizations) for e in out_exprs] if isinstance(exprs, Matrix): out_exprs = Matrix(exprs.rows, exprs.cols, out_exprs) if postprocess is None: return ordered_iv_se.items(), out_exprs return postprocess(ordered_iv_se.items(), out_exprs)
def find_bac_cab(expr): """ Given a vector expression, find the terms satisfying the pattern: B * (A & C) - C * (A & B) where & is the dot product. The list is ordered in such a way that nested matches comes first (similarly to scanning the expression tree from bottom to top). """ def _check_pairs(terms): # c1 = Mul(*[a for a in terms[0].args if not hasattr(a, "is_Vector_Scalar")]) # c2 = Mul(*[a for a in terms[1].args if not hasattr(a, "is_Vector_Scalar")]) c1 = Mul(*[ a for a in terms[0].args if not isinstance(a, (Vector, VectorExpr)) ]) c2 = Mul(*[ a for a in terms[1].args if not isinstance(a, (Vector, VectorExpr)) ]) n1, _ = c1.as_coeff_mul() n2, _ = c2.as_coeff_mul() if n1 * n2 > 0: # opposite sign return False if Abs(c1) != Abs(c2): return False # v1 = [a for a in terms[0].args if (hasattr(a, "is_Vector_Scalar") and a.is_Vector)][0] # v2 = [a for a in terms[1].args if (hasattr(a, "is_Vector_Scalar") and a.is_Vector)][0] v1 = [ a for a in terms[0].args if (isinstance(a, (Vector, VectorExpr)) and a.is_Vector) ][0] v2 = [ a for a in terms[1].args if (isinstance(a, (Vector, VectorExpr)) and a.is_Vector) ][0] if v1 == v2: return False dot1 = [a for a in terms[0].args if isinstance(a, VecDot)][0] dot2 = [a for a in terms[1].args if isinstance(a, VecDot)][0] if not ((v1 in dot2.args) and (v2 in dot1.args)): return False v = list(set(dot1.args).intersection(set(dot2.args)))[0] if v == v1 or v == v2: return False return True def _check(arg): if (isinstance(arg, VecMul) and any([a.is_Vector for a in arg.args]) and any([isinstance(a, VecDot) for a in arg.args])): return True return False found = set() for arg in preorder_traversal(expr): possible_args = list(filter(_check, arg.args)) # print("possible_args", possible_args) # for p in possible_args: # print("\t", p.func, p.is_Vector, p) # # possible_args = list(arg.find(A * (B & C))) # # possible_args = list(filter(lambda x: isinstance(x, VecMul), possible_args)) if len(possible_args) > 1: combs = list(combinations(possible_args, 2)) # print("COMBINATIONS", combs) for c in combs: # print("\tC", c) # print("\n\t\t".join(str(a.func) + ", " + str(a.is_Vector) + ", " + str(a) for a in c)) if _check_pairs(c): # print("\t\t proceeding") found.add(VecAdd(*c)) found = list(ordered(list(found))) return found
def cse(exprs, symbols=None, optimizations=None, postprocess=None): """ Perform common subexpression elimination on an expression. Parameters ========== exprs : list of sympy expressions, or a single sympy expression The expressions to reduce. symbols : infinite iterator yielding unique Symbols The symbols used to label the common subexpressions which are pulled out. The ``numbered_symbols`` generator is useful. The default is a stream of symbols of the form "x0", "x1", etc. This must be an infinite iterator. optimizations : list of (callable, callable) pairs, optional The (preprocessor, postprocessor) pairs. If not provided, ``sympy.simplify.cse.cse_optimizations`` is used. postprocess : a function which accepts the two return values of cse and returns the desired form of output from cse, e.g. if you want the replacements reversed the function might be the following lambda: lambda r, e: return reversed(r), e Returns ======= replacements : list of (Symbol, expression) pairs All of the common subexpressions that were replaced. Subexpressions earlier in this list might show up in subexpressions later in this list. reduced_exprs : list of sympy expressions The reduced expressions with all of the replacements above. """ from sympy.matrices import Matrix if symbols is None: symbols = numbered_symbols() else: # In case we get passed an iterable with an __iter__ method instead of # an actual iterator. symbols = iter(symbols) seen_subexp = set() muls = set() adds = set() to_eliminate = set() if optimizations is None: # Pull out the default here just in case there are some weird # manipulations of the module-level list in some other thread. optimizations = list(cse_optimizations) # Handle the case if just one expression was passed. if isinstance(exprs, Basic): exprs = [exprs] # Preprocess the expressions to give us better optimization opportunities. reduced_exprs = [preprocess_for_cse(e, optimizations) for e in exprs] # Find all of the repeated subexpressions. for expr in reduced_exprs: if not isinstance(expr, Basic): continue pt = preorder_traversal(expr) for subtree in pt: inv = 1/subtree if subtree.is_Pow else None if subtree.is_Atom or iterable(subtree) or inv and inv.is_Atom: # Exclude atoms, since there is no point in renaming them. continue if subtree in seen_subexp: if inv and _coeff_isneg(subtree.exp): # save the form with positive exponent subtree = inv to_eliminate.add(subtree) pt.skip() continue if inv and inv in seen_subexp: if _coeff_isneg(subtree.exp): # save the form with positive exponent subtree = inv to_eliminate.add(subtree) pt.skip() continue elif subtree.is_Mul: muls.add(subtree) elif subtree.is_Add: adds.add(subtree) seen_subexp.add(subtree) # process adds - any adds that weren't repeated might contain # subpatterns that are repeated, e.g. x+y+z and x+y have x+y in common adds = [set(a.args) for a in ordered(adds)] for i in xrange(len(adds)): for j in xrange(i + 1, len(adds)): com = adds[i].intersection(adds[j]) if len(com) > 1: to_eliminate.add(Add(*com)) # remove this set of symbols so it doesn't appear again adds[i] = adds[i].difference(com) adds[j] = adds[j].difference(com) for k in xrange(j + 1, len(adds)): if not com.difference(adds[k]): adds[k] = adds[k].difference(com) # process muls - any muls that weren't repeated might contain # subpatterns that are repeated, e.g. x*y*z and x*y have x*y in common # use SequenceMatcher on the nc part to find the longest common expression # in common between the two nc parts sm = difflib.SequenceMatcher() muls = [a.args_cnc(cset=True) for a in ordered(muls)] for i in xrange(len(muls)): if muls[i][1]: sm.set_seq1(muls[i][1]) for j in xrange(i + 1, len(muls)): # the commutative part in common ccom = muls[i][0].intersection(muls[j][0]) # the non-commutative part in common if muls[i][1] and muls[j][1]: # see if there is any chance of an nc match ncom = set(muls[i][1]).intersection(set(muls[j][1])) if len(ccom) + len(ncom) < 2: continue # now work harder to find the match sm.set_seq2(muls[j][1]) i1, _, n = sm.find_longest_match(0, len(muls[i][1]), 0, len(muls[j][1])) ncom = muls[i][1][i1:i1 + n] else: ncom = [] com = list(ccom) + ncom if len(com) < 2: continue to_eliminate.add(Mul(*com)) # remove ccom from all if there was no ncom; to update the nc part # would require finding the subexpr and then replacing it with a # dummy to keep bounding nc symbols from being identified as a # subexpr, e.g. removing B*C from A*B*C*D might allow A*D to be # identified as a subexpr which would not be right. if not ncom: muls[i][0] = muls[i][0].difference(ccom) for k in xrange(j, len(muls)): if not ccom.difference(muls[k][0]): muls[k][0] = muls[k][0].difference(ccom) # make to_eliminate canonical; we will prefer non-Muls to Muls # so select them first (non-Muls will have False for is_Mul and will # be first in the ordering. to_eliminate = list(ordered(to_eliminate, lambda _: _.is_Mul)) # Substitute symbols for all of the repeated subexpressions. replacements = [] reduced_exprs = list(reduced_exprs) hit = True for i, subtree in enumerate(to_eliminate): if hit: sym = next(symbols) hit = False if subtree.is_Pow and subtree.exp.is_Rational: update = lambda x: x.xreplace({subtree: sym, 1/subtree: 1/sym}) else: update = lambda x: x.subs(subtree, sym) # Make the substitution in all of the target expressions. for j, expr in enumerate(reduced_exprs): old = reduced_exprs[j] reduced_exprs[j] = update(expr) hit = hit or (old != reduced_exprs[j]) # Make the substitution in all of the subsequent substitutions. for j in range(i + 1, len(to_eliminate)): old = to_eliminate[j] to_eliminate[j] = update(to_eliminate[j]) hit = hit or (old != to_eliminate[j]) if hit: replacements.append((sym, subtree)) # Postprocess the expressions to return the expressions to canonical form. for i, (sym, subtree) in enumerate(replacements): subtree = postprocess_for_cse(subtree, optimizations) replacements[i] = (sym, subtree) reduced_exprs = [postprocess_for_cse(e, optimizations) for e in reduced_exprs] # remove replacements that weren't used more than once _remove_singletons(replacements, reduced_exprs) if isinstance(exprs, Matrix): reduced_exprs = [Matrix(exprs.rows, exprs.cols, reduced_exprs)] if postprocess is None: return replacements, reduced_exprs return postprocess(replacements, reduced_exprs)
def _neq_linear_first_order_const_coeff_homogeneous(match_): r""" System of n first-order constant-coefficient linear homogeneous differential equations .. math:: y'_k = a_{k1} y_1 + a_{k2} y_2 +...+ a_{kn} y_n; k = 1,2,...,n or that can be written as `\vec{y'} = A . \vec{y}` where `\vec{y}` is matrix of `y_k` for `k = 1,2,...n` and `A` is a `n \times n` matrix. Since these equations are equivalent to a first order homogeneous linear differential equation. So the general solution will contain `n` linearly independent parts and solution will consist some type of exponential functions. Assuming `y = \vec{v} e^{rt}` is a solution of the system where `\vec{v}` is a vector of coefficients of `y_1,...,y_n`. Substituting `y` and `y' = r v e^{r t}` into the equation `\vec{y'} = A . \vec{y}`, we get .. math:: r \vec{v} e^{rt} = A \vec{v} e^{rt} .. math:: r \vec{v} = A \vec{v} where `r` comes out to be eigenvalue of `A` and vector `\vec{v}` is the eigenvector of `A` corresponding to `r`. There are three possibilities of eigenvalues of `A` - `n` distinct real eigenvalues - complex conjugate eigenvalues - eigenvalues with multiplicity `k` 1. When all eigenvalues `r_1,..,r_n` are distinct with `n` different eigenvectors `v_1,...v_n` then the solution is given by .. math:: \vec{y} = C_1 e^{r_1 t} \vec{v_1} + C_2 e^{r_2 t} \vec{v_2} +...+ C_n e^{r_n t} \vec{v_n} where `C_1,C_2,...,C_n` are arbitrary constants. 2. When some eigenvalues are complex then in order to make the solution real, we take a linear combination: if `r = a + bi` has an eigenvector `\vec{v} = \vec{w_1} + i \vec{w_2}` then to obtain real-valued solutions to the system, replace the complex-valued solutions `e^{rx} \vec{v}` with real-valued solution `e^{ax} (\vec{w_1} \cos(bx) - \vec{w_2} \sin(bx))` and for `r = a - bi` replace the solution `e^{-r x} \vec{v}` with `e^{ax} (\vec{w_1} \sin(bx) + \vec{w_2} \cos(bx))` 3. If some eigenvalues are repeated. Then we get fewer than `n` linearly independent eigenvectors, we miss some of the solutions and need to construct the missing ones. We do this via generalized eigenvectors, vectors which are not eigenvectors but are close enough that we can use to write down the remaining solutions. For a eigenvalue `r` with eigenvector `\vec{w}` we obtain `\vec{w_2},...,\vec{w_k}` using .. math:: (A - r I) . \vec{w_2} = \vec{w} .. math:: (A - r I) . \vec{w_3} = \vec{w_2} .. math:: \vdots .. math:: (A - r I) . \vec{w_k} = \vec{w_{k-1}} Then the solutions to the system for the eigenspace are `e^{rt} [\vec{w}], e^{rt} [t \vec{w} + \vec{w_2}], e^{rt} [\frac{t^2}{2} \vec{w} + t \vec{w_2} + \vec{w_3}], ...,e^{rt} [\frac{t^{k-1}}{(k-1)!} \vec{w} + \frac{t^{k-2}}{(k-2)!} \vec{w_2} +...+ t \vec{w_{k-1}} + \vec{w_k}]` So, If `\vec{y_1},...,\vec{y_n}` are `n` solution of obtained from three categories of `A`, then general solution to the system `\vec{y'} = A . \vec{y}` .. math:: \vec{y} = C_1 \vec{y_1} + C_2 \vec{y_2} + \cdots + C_n \vec{y_n} """ eq = match_['eq'] func = match_['func'] fc = match_['func_coeff'] n = len(eq) t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] constants = numbered_symbols(prefix='C', cls=Symbol, start=1) # This needs to be modified in future so that fc is only of type Matrix M = -fc if type(fc) is Matrix else Matrix(n, n, lambda i, j: -fc[i, func[j], 0]) P, J = matrix_exp_jordan_form(M, t) P = simplify(P) Cvect = Matrix(list(next(constants) for _ in range(n))) sol_vector = P * (J * Cvect) sol_vector = [ collect(s, ordered(J.atoms(exp)), exact=True) for s in sol_vector ] sol_dict = [Eq(func[i], sol_vector[i]) for i in range(n)] return sol_dict
def __repr__(self): # Factors return "Factors({%s})" % ", ".join(["%s: %s" % (k, v) for k, v in ordered(self.factors.items())])
def collect_const(expr, *vars, **kwargs): """ This is the very same code of sympy.simplify.radsimp.py collect_const, with modification: the original method used Mul._from_args and Add._from_args, which do not call a post-processor, hence I obtained the wrong result. Here, I use VecAdd, VecMul... """ if not expr.is_Add: return expr recurse = False Numbers = kwargs.get('Numbers', True) if not vars: recurse = True vars = set() for a in expr.args: for m in Mul.make_args(a): if m.is_number: vars.add(m) else: vars = sympify(vars) if not Numbers: vars = [v for v in vars if not v.is_Number] vars = list(ordered(vars)) for v in vars: terms = defaultdict(list) Fv = Factors(v) for m in Add.make_args(expr): f = Factors(m) q, r = f.div(Fv) if r.is_one: # only accept this as a true factor if # it didn't change an exponent from an Integer # to a non-Integer, e.g. 2/sqrt(2) -> sqrt(2) # -- we aren't looking for this sort of change fwas = f.factors.copy() fnow = q.factors if not any(k in fwas and fwas[k].is_Integer and not fnow[k].is_Integer for k in fnow): terms[v].append(q.as_expr()) continue terms[S.One].append(m) args = [] hit = False uneval = False for k in ordered(terms): v = terms[k] if k is S.One: args.extend(v) continue if len(v) > 1: v = Add(*v) hit = True if recurse and v != expr: vars.append(v) else: v = v[0] # be careful not to let uneval become True unless # it must be because it's going to be more expensive # to rebuild the expression as an unevaluated one if Numbers and k.is_Number and v.is_Add: # args.append(_keep_coeff(k, v, sign=True)) args.append(VecMul(*[k, v], evaluate=False)) uneval = True else: args.append(k * v) if hit: if uneval: # expr = Add(*args) expr = VecAdd(*args, evaluate=False) else: # expr = Add(*args) expr = VecAdd(*args) if not expr.is_Add: break return expr
def piecewise_simplify(expr, **kwargs): expr = piecewise_simplify_arguments(expr, **kwargs) args = list(expr.args) _blessed = lambda e: getattr(e.lhs, '_diff_wrt', False) and (getattr( e.rhs, '_diff_wrt', None) or isinstance(e.rhs, (Rational, NumberSymbol))) for i, (expr, cond) in enumerate(args): # try to simplify conditions and the expression for # equalities that are part of the condition, e.g. # Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) # -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if _blessed(e): expr = expr.subs(*e.args) eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] other = [ei.subs(*e.args) for ei in other] cond = And(*(eqs + other)) args[i] = args[i].func(expr, cond) # See if expressions valid for an Equal expression happens to evaluate # to the same function as in the next piecewise segment, see: # https://github.com/sympy/sympy/issues/8458 prevexpr = None for i, (expr, cond) in reversed(list(enumerate(args))): if prevexpr is not None: if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] _prevexpr = prevexpr _expr = expr if eqs and not other: eqs = list(ordered(eqs)) for e in eqs: # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if _blessed(e): _prevexpr = _prevexpr.subs(*e.args) _expr = _expr.subs(*e.args) # Did it evaluate to the same? if _prevexpr == _expr: # Set the expression for the Not equal section to the same # as the next. These will be merged when creating the new # Piecewise args[i] = args[i].func(args[i + 1][0], cond) else: # Update the expression that we compare against prevexpr = expr else: prevexpr = expr return Piecewise(*args)
def _match_common_args(Func, funcs): if order != 'none': funcs = list(ordered(funcs)) else: funcs = sorted(funcs, key=lambda x: len(x.args)) if Func is Mul: F = Pow meth = 'as_powers_dict' from sympy.core.add import _addsort as inplace_sorter elif Func is Add: F = Mul meth = 'as_coefficients_dict' from sympy.core.mul import _mulsort as inplace_sorter else: assert None # expected Mul or Add # ----------------- helpers --------------------------- def ufunc(*args): # return a well formed unevaluated function from the args # SHARES Func, inplace_sorter args = list(args) inplace_sorter(args) return Func(*args, evaluate=False) def as_dict(e): # creates a dictionary of the expression using either # as_coefficients_dict or as_powers_dict, depending on Func # SHARES meth d = getattr(e, meth, lambda: {a: S.One for a in e.args})() for k in list(d.keys()): try: as_int(d[k]) except ValueError: d[F(k, d.pop(k))] = S.One return d def from_dict(d): # build expression from dict from # as_coefficients_dict or as_powers_dict # SHARES F return ufunc(*[F(k, v) for k, v in d.items()]) def update(k): # updates all of the info associated with k using # the com_dict: func_dicts, func_args, opt_subs # returns True if all values were updated, else None # SHARES com_dict, com_func, func_dicts, func_args, # opt_subs, funcs, verbose for di in com_dict: # don't allow a sign to change if com_dict[di] > func_dicts[k][di]: return # remove it if Func is Add: take = min(func_dicts[k][i] for i in com_dict) _sum = from_dict(com_dict) if take == 1: com_func_take = _sum else: com_func_take = Mul(take, _sum, evaluate=False) else: take = igcd(*[func_dicts[k][i] for i in com_dict]) base = from_dict(com_dict) if take == 1: com_func_take = base else: com_func_take = Pow(base, take, evaluate=False) for di in com_dict: func_dicts[k][di] -= take * com_dict[di] # compute the remaining expression rem = from_dict(func_dicts[k]) # reject hollow change, e.g extracting x + 1 from x + 3 if Func is Add and rem and rem.is_Integer and 1 in com_dict: return if verbose: print('\nfunc %s (%s) \ncontains %s \nas %s \nleaving %s' % (funcs[k], func_dicts[k], com_func, com_func_take, rem)) # recompute the dict since some keys may now # have corresponding values of 0; one could # keep track of which ones went to zero but # this seems cleaner func_dicts[k] = as_dict(rem) # update associated info func_dicts[k][com_func] = take func_args[k] = set(func_dicts[k]) # keep the constant separate from the remaining # part of the expression, e.g. 2*(a*b) rather than 2*a*b opt_subs[funcs[k]] = ufunc(rem, com_func_take) # everything was updated return True def get_copy(i): return [func_dicts[i].copy(), func_args[i].copy(), funcs[i], i] def restore(dafi): i = dafi.pop() func_dicts[i], func_args[i], funcs[i] = dafi # ----------------- end helpers ----------------------- func_dicts = [as_dict(f) for f in funcs] func_args = [set(d) for d in func_dicts] while True: hit = pairwise_most_common(func_args) if not hit or len(hit[0][0]) <= 1: break changed = False for com_args, ij in hit: take = len(com_args) ALL = list(ordered(com_args)) while take >= 2: for com_args in subsets(ALL, take): com_func = Func(*com_args) com_dict = as_dict(com_func) for i, j in ij: dafi = None if com_func != funcs[i]: dafi = get_copy(i) ch = update(i) if not ch: restore(dafi) continue if com_func != funcs[j]: dafj = get_copy(j) ch = update(j) if not ch: if dafi is not None: restore(dafi) restore(dafj) continue changed = True if changed: break else: take -= 1 continue break else: continue break if not changed: break
def cse(exprs, symbols=None, optimizations=None, postprocess=None): """ Perform common subexpression elimination on an expression. Parameters ========== exprs : list of sympy expressions, or a single sympy expression The expressions to reduce. symbols : infinite iterator yielding unique Symbols The symbols used to label the common subexpressions which are pulled out. The ``numbered_symbols`` generator is useful. The default is a stream of symbols of the form "x0", "x1", etc. This must be an infinite iterator. optimizations : list of (callable, callable) pairs, optional The (preprocessor, postprocessor) pairs. If not provided, ``sympy.simplify.cse.cse_optimizations`` is used. postprocess : a function which accepts the two return values of cse and returns the desired form of output from cse, e.g. if you want the replacements reversed the function might be the following lambda: lambda r, e: return reversed(r), e Returns ======= replacements : list of (Symbol, expression) pairs All of the common subexpressions that were replaced. Subexpressions earlier in this list might show up in subexpressions later in this list. reduced_exprs : list of sympy expressions The reduced expressions with all of the replacements above. """ from sympy.matrices import Matrix if symbols is None: symbols = numbered_symbols() else: # In case we get passed an iterable with an __iter__ method instead of # an actual iterator. symbols = iter(symbols) seen_subexp = set() muls = set() adds = set() to_eliminate = set() if optimizations is None: # Pull out the default here just in case there are some weird # manipulations of the module-level list in some other thread. optimizations = list(cse_optimizations) # Handle the case if just one expression was passed. if isinstance(exprs, Basic): exprs = [exprs] # Preprocess the expressions to give us better optimization opportunities. reduced_exprs = [preprocess_for_cse(e, optimizations) for e in exprs] # Find all of the repeated subexpressions. for expr in reduced_exprs: if not isinstance(expr, Basic): continue pt = preorder_traversal(expr) for subtree in pt: inv = 1 / subtree if subtree.is_Pow else None if subtree.is_Atom or iterable(subtree) or inv and inv.is_Atom: # Exclude atoms, since there is no point in renaming them. continue if subtree in seen_subexp: if inv and _coeff_isneg(subtree.exp): # save the form with positive exponent subtree = inv to_eliminate.add(subtree) pt.skip() continue if inv and inv in seen_subexp: if _coeff_isneg(subtree.exp): # save the form with positive exponent subtree = inv to_eliminate.add(subtree) pt.skip() continue elif subtree.is_Mul: muls.add(subtree) elif subtree.is_Add: adds.add(subtree) seen_subexp.add(subtree) # process adds - any adds that weren't repeated might contain # subpatterns that are repeated, e.g. x+y+z and x+y have x+y in common adds = [set(a.args) for a in ordered(adds)] for i in xrange(len(adds)): for j in xrange(i + 1, len(adds)): com = adds[i].intersection(adds[j]) if len(com) > 1: to_eliminate.add(Add(*com)) # remove this set of symbols so it doesn't appear again adds[i] = adds[i].difference(com) adds[j] = adds[j].difference(com) for k in xrange(j + 1, len(adds)): if not com.difference(adds[k]): adds[k] = adds[k].difference(com) # process muls - any muls that weren't repeated might contain # subpatterns that are repeated, e.g. x*y*z and x*y have x*y in common # use SequenceMatcher on the nc part to find the longest common expression # in common between the two nc parts sm = difflib.SequenceMatcher() muls = [a.args_cnc(cset=True) for a in ordered(muls)] for i in xrange(len(muls)): if muls[i][1]: sm.set_seq1(muls[i][1]) for j in xrange(i + 1, len(muls)): # the commutative part in common ccom = muls[i][0].intersection(muls[j][0]) # the non-commutative part in common if muls[i][1] and muls[j][1]: # see if there is any chance of an nc match ncom = set(muls[i][1]).intersection(set(muls[j][1])) if len(ccom) + len(ncom) < 2: continue # now work harder to find the match sm.set_seq2(muls[j][1]) i1, _, n = sm.find_longest_match(0, len(muls[i][1]), 0, len(muls[j][1])) ncom = muls[i][1][i1:i1 + n] else: ncom = [] com = list(ccom) + ncom if len(com) < 2: continue to_eliminate.add(Mul(*com)) # remove ccom from all if there was no ncom; to update the nc part # would require finding the subexpr and then replacing it with a # dummy to keep bounding nc symbols from being identified as a # subexpr, e.g. removing B*C from A*B*C*D might allow A*D to be # identified as a subexpr which would not be right. if not ncom: muls[i][0] = muls[i][0].difference(ccom) for k in xrange(j, len(muls)): if not ccom.difference(muls[k][0]): muls[k][0] = muls[k][0].difference(ccom) # make to_eliminate canonical; we will prefer non-Muls to Muls # so select them first (non-Muls will have False for is_Mul and will # be first in the ordering. to_eliminate = list(ordered(to_eliminate, lambda _: _.is_Mul)) # Substitute symbols for all of the repeated subexpressions. replacements = [] reduced_exprs = list(reduced_exprs) hit = True for i, subtree in enumerate(to_eliminate): if hit: sym = next(symbols) hit = False if subtree.is_Pow and subtree.exp.is_Rational: update = lambda x: x.xreplace({subtree: sym, 1 / subtree: 1 / sym}) else: update = lambda x: x.subs(subtree, sym) # Make the substitution in all of the target expressions. for j, expr in enumerate(reduced_exprs): old = reduced_exprs[j] reduced_exprs[j] = update(expr) hit = hit or (old != reduced_exprs[j]) # Make the substitution in all of the subsequent substitutions. for j in range(i + 1, len(to_eliminate)): old = to_eliminate[j] to_eliminate[j] = update(to_eliminate[j]) hit = hit or (old != to_eliminate[j]) if hit: replacements.append((sym, subtree)) # Postprocess the expressions to return the expressions to canonical form. for i, (sym, subtree) in enumerate(replacements): subtree = postprocess_for_cse(subtree, optimizations) replacements[i] = (sym, subtree) reduced_exprs = [ postprocess_for_cse(e, optimizations) for e in reduced_exprs ] # remove replacements that weren't used more than once _remove_singletons(replacements, reduced_exprs) if isinstance(exprs, Matrix): reduced_exprs = [Matrix(exprs.rows, exprs.cols, reduced_exprs)] if postprocess is None: return replacements, reduced_exprs return postprocess(replacements, reduced_exprs)
def linodesolve(A, t, b=None, B=None, type="auto", doit=False): r""" System of n equations linear first-order differential equations Explanation =========== This solver solves the system of ODEs of the follwing form: .. math:: X'(t) = A(t) X(t) + b(t) Here, $A(t)$ is the coefficient matrix, $X(t)$ is the vector of n independent variables, $b(t)$ is the non-homogeneous term and $X'(t)$ is the derivative of $X(t)$ Depending on the properties of $A(t)$ and $b(t)$, this solver evaluates the solution differently. When $A(t)$ is constant coefficient matrix and $b(t)$ is zero vector i.e. system is homogeneous, the solution is: .. math:: X(t) = \exp(A t) C Here, $C$ is a vector of constants and $A$ is the constant coefficient matrix. When $A(t)$ is constant coefficient matrix and $b(t)$ is non-zero i.e. system is non-homogeneous, the solution is: .. math:: X(t) = e^{A t} ( \int e^{- A t} b \,dt + C) When $A(t)$ is coefficient matrix such that its commutative with its antiderivative $B(t)$ and $b(t)$ is a zero vector i.e. system is homogeneous, the solution is: .. math:: X(t) = \exp(B(t)) C When $A(t)$ is commutative with its antiderivative $B(t)$ and $b(t)$ is non-zero i.e. system is non-homogeneous, the solution is: .. math:: X(t) = e^{B(t)} ( \int e^{-B(t)} b(t) \,dt + C) The final solution is the general solution for all the four equations since a constant coefficient matrix is always commutative with its antidervative. Parameters ========== A : Matrix Coefficient matrix of the system of linear first order ODEs. t : Symbol Independent variable in the system of ODEs. b : Matrix or None Non-homogeneous term in the system of ODEs. If None is passed, a homogeneous system of ODEs is assumed. B : Matrix or None Antiderivative of the coefficient matrix. If the antiderivative is not passed and the solution requires the term, then the solver would compute it internally. type : String Type of the system of ODEs passed. Depending on the type, the solution is evaluated. The type values allowed and the corresponding system it solves are: "type1" for constant coefficient homogeneous "type2" for constant coefficient non-homogeneous, "type3" for non-constant coefficient homogeneous and "type4" for non-constant coefficient non-homogeneous. The default value is "auto" which will let the solver decide the correct type of the system passed. doit : Boolean Evaluate the solution if True, default value is False Examples ======== To solve the system of ODEs using this function directly, several things must be done in the right order. Wrong inputs to the function will lead to incorrect results. >>> from sympy import symbols, Function, Eq >>> from sympy.solvers.ode.systems import canonical_odes, linear_ode_to_matrix, linodesolve, linodesolve_type >>> from sympy.solvers.ode.subscheck import checkodesol >>> f, g = symbols("f, g", cls=Function) >>> x, a = symbols("x, a") >>> funcs = [f(x), g(x)] >>> eqs = [Eq(f(x).diff(x) - f(x), a*g(x) + 1), Eq(g(x).diff(x) + g(x), a*f(x))] Here, it is important to note that before we derive the coefficient matrix, it is important to get the system of ODEs into the desired form. For that we will use :obj:`sympy.solvers.ode.systems.canonical_odes()`. >>> eqs = canonical_odes(eqs, funcs, x) >>> eqs [[Eq(Derivative(f(x), x), a*g(x) + f(x) + 1), Eq(Derivative(g(x), x), a*f(x) - g(x))]] Now, we will use :obj:`sympy.solvers.ode.systems.linear_ode_to_matrix()` to get the coefficient matrix and the non-homogeneous term if it is there. >>> eqs = eqs[0] >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1) >>> A = A0 We have the coefficient matrices and the non-homogeneous term ready. Now, we can use :obj:`sympy.solvers.ode.systems.linodesolve_type()` to get the information for the system of ODEs to finally pass it to the solver. >>> system_info = linodesolve_type(A, x, b=b) >>> sol_vector = linodesolve(A, x, b=b, B=system_info['antiderivative'], type=system_info['type']) Now, we can prove if the solution is correct or not by using :obj:`sympy.solvers.ode.checkodesol()` >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] >>> checkodesol(eqs, sol) (True, [0, 0]) We can also use the doit method to evaluate the solutions passed by the function. >>> sol_vector_evaluated = linodesolve(A, x, b=b, type="type2", doit=True) Now, we will look at a system of ODEs which is non-constant. >>> eqs = [Eq(f(x).diff(x), f(x) + x*g(x)), Eq(g(x).diff(x), -x*f(x) + g(x))] The system defined above is already in the desired form, so we don't have to convert it. >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1) >>> A = A0 A user can also pass the commutative antiderivative required for type3 and type4 system of ODEs. Passing an incorrect one will lead to incorrect results. If the coefficient matrix is not commutative with its antiderivative, then :obj:`sympy.solvers.ode.systems.linodesolve_type()` raises a NotImplementedError. If it does have a commutative antiderivative, then the function just returns the information about the system. >>> system_info = linodesolve_type(A, x, b=b) Now, we can pass the antiderivative as an argument to get the solution. If the system information is not passed, then the solver will compute the required arguments internally. >>> sol_vector = linodesolve(A, x, b=b) Once again, we can verify the solution obtained. >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] >>> checkodesol(eqs, sol) (True, [0, 0]) Returns ======= List Raises ====== ValueError This error is raised when the coefficient matrix, non-homogeneous term or the antiderivative, if passed, aren't a matrix or don't have correct dimensions NonSquareMatrixError When the coefficient matrix or its antiderivative, if passed isn't a square matrix NotImplementedError If the coefficient matrix doesn't have a commutative antiderivative See Also ======== linear_ode_to_matrix: Coefficient matrix computation function canonical_odes: System of ODEs representation change linodesolve_type: Getting information about systems of ODEs to pass in this solver """ if not isinstance(A, MatrixBase): raise ValueError( filldedent('''\ The coefficients of the system of ODEs should be of type Matrix ''')) if not A.is_square: raise NonSquareMatrixError( filldedent('''\ The coefficient matrix must be a square ''')) if b is not None: if not isinstance(b, MatrixBase): raise ValueError( filldedent('''\ The non-homogeneous terms of the system of ODEs should be of type Matrix ''')) if A.rows != b.rows: raise ValueError( filldedent('''\ The system of ODEs should have the same number of non-homogeneous terms and the number of equations ''')) if B is not None: if not isinstance(B, MatrixBase): raise ValueError( filldedent('''\ The antiderivative of coefficients of the system of ODEs should be of type Matrix ''')) if not B.is_square: raise NonSquareMatrixError( filldedent('''\ The antiderivative of the coefficient matrix must be a square ''')) if A.rows != B.rows: raise ValueError( filldedent('''\ The coefficient matrix and its antiderivative should have same dimensions ''')) if not any(type == "type{}".format(i) for i in range(1, 5)) and not type == "auto": raise ValueError( filldedent('''\ The input type should be a valid one ''')) n = A.rows # constants = numbered_symbols(prefix='C', cls=Dummy, start=const_idx+1) Cvect = Matrix(list(Dummy() for _ in range(n))) if (type == "type2" or type == "type4") and b is None: b = zeros(n, 1) if type == "auto": system_info = linodesolve_type(A, t, b=b) type = system_info["type"] B = system_info["antiderivative"] if type == "type1" or type == "type2": P, J = matrix_exp_jordan_form(A, t) P = simplify(P) if type == "type1": sol_vector = P * (J * Cvect) else: sol_vector = P * J * ( (J.inv() * P.inv() * b).applyfunc(lambda x: Integral(x, t)) + Cvect) else: if B is None: B, _ = _is_commutative_anti_derivative(A, t) if type == "type3": sol_vector = B.exp() * Cvect else: sol_vector = B.exp() * (( (-B).exp() * b).applyfunc(lambda x: Integral(x, t)) + Cvect) gens = sol_vector.atoms(exp) if type != "type1": sol_vector = [expand_mul(s) for s in sol_vector] sol_vector = [collect(s, ordered(gens), exact=True) for s in sol_vector] if doit: sol_vector = [s.doit() for s in sol_vector] return sol_vector