Beispiel #1
0
    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
Beispiel #2
0
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
Beispiel #3
0
    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)
Beispiel #4
0
 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 _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)
Beispiel #6
0
 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)
Beispiel #7
0
    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
Beispiel #8
0
    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
Beispiel #9
0
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
Beispiel #10
0
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))))
Beispiel #11
0
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
Beispiel #12
0
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)))
Beispiel #13
0
 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
Beispiel #14
0
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)))
Beispiel #15
0
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)
Beispiel #16
0
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
Beispiel #17
0
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
Beispiel #18
0
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)
Beispiel #19
0
    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))
Beispiel #20
0
 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)
Beispiel #21
0
    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
Beispiel #22
0
 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)
Beispiel #23
0
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)
Beispiel #24
0
 def __hash__(self):  # Factors
     keys = tuple(ordered(self.factors.keys()))
     values = [self.factors[k] for k in keys]
     return hash((keys, values))
Beispiel #25
0
 def __repr__(self):  # Factors
     return "Factors({%s})" % ', '.join(
         ['%s: %s' % (k, v) for k, v in ordered(self.factors.items())])
Beispiel #26
0
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)
Beispiel #27
0
 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)
Beispiel #28
0
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
Beispiel #29
0
    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
Beispiel #30
0
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
Beispiel #31
0
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)
Beispiel #32
0
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
Beispiel #33
0
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)
Beispiel #34
0
 def __hash__(self):  # Factors
     keys = tuple(ordered(self.factors.keys()))
     values = [self.factors[k] for k in keys]
     return hash((keys, values))
Beispiel #35
0
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
Beispiel #36
0
    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
Beispiel #37
0
 def __repr__(self):  # Factors
     return "Factors({%s})" % ", ".join(["%s: %s" % (k, v) for k, v in ordered(self.factors.items())])
Beispiel #38
0
 def __repr__(self):  # Factors
     return "Factors({%s})" % ', '.join(
         ['%s: %s' % (k, v) for k, v in ordered(self.factors.items())])
Beispiel #39
0
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
Beispiel #40
0
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)
Beispiel #41
0
    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
Beispiel #42
0
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)
Beispiel #43
0
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