def test_apart_list(): assert apart_list(1) == 1 w0, w1, w2 = Symbol("w0"), Symbol("w1"), Symbol("w2") _a = Dummy("a") f = (-2*x - 2*x**2) / (3*x**2 - 6*x) assert (apart_list(f, x, dummies=numbered_symbols("w")) == (-1, Poly(Rational(2, 3), x), [(Poly(w0 - 2, w0), Lambda(_a, 2), Lambda(_a, -_a + x), 1)])) assert (apart_list(2/(x**2-2), x, dummies=numbered_symbols("w")) == (1, Poly(0, x), [(Poly(w0**2 - 2, w0), Lambda(_a, _a/2), Lambda(_a, -_a + x), 1)])) f = 36 / (x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2) assert (apart_list(f, x, dummies=numbered_symbols("w")) == (1, Poly(0, x), [(Poly(w0 - 2, w0), Lambda(_a, 4), Lambda(_a, -_a + x), 1), (Poly(w1**2 - 1, w1), Lambda(_a, -3*_a - 6), Lambda(_a, -_a + x), 2), (Poly(w2 + 1, w2), Lambda(_a, -4), Lambda(_a, -_a + x), 1)])) f = 1/(2*(x - 1)**2) assert (apart_list(f, x, dummies=numbered_symbols("w")) == (1, Poly(0, x), [(Poly(2, w0), Lambda(_a, 0), Lambda(_a, x - _a), 1), (Poly(w1 - 1, w1), Lambda(_a, Rational(1, 2)), Lambda(_a, x - _a), 2), (Poly(1, w2), Lambda(_a, 0), Lambda(_a, x - _a), 1)]))
def test_take(): X = numbered_symbols() assert take(X, 5) == list(symbols('x0:5')) assert take(X, 5) == list(symbols('x5:10')) assert take([1, 2, 3, 4, 5], 5) == [1, 2, 3, 4, 5]
def test_apart_list(): from diofant.utilities.iterables import numbered_symbols assert apart_list(1) == 1 w0, w1, w2 = Symbol("w0"), Symbol("w1"), Symbol("w2") _a = Dummy("a") f = (-2 * x - 2 * x**2) / (3 * x**2 - 6 * x) assert apart_list(f, x, dummies=numbered_symbols("w")) == (-1, Poly(Rational(2, 3), x, domain='QQ'), [(Poly(w0 - 2, w0, domain='ZZ'), Lambda(_a, 2), Lambda(_a, -_a + x), 1)]) assert apart_list( 2 / (x**2 - 2), x, dummies=numbered_symbols("w")) == (1, Poly(0, x, domain='ZZ'), [ (Poly(w0**2 - 2, w0, domain='ZZ'), Lambda(_a, _a / 2), Lambda(_a, -_a + x), 1) ]) f = 36 / (x**5 - 2 * x**4 - 2 * x**3 + 4 * x**2 + x - 2) assert apart_list( f, x, dummies=numbered_symbols("w")) == (1, Poly(0, x, domain='ZZ'), [ (Poly(w0 - 2, w0, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1), (Poly(w1**2 - 1, w1, domain='ZZ'), Lambda(_a, -3 * _a - 6), Lambda(_a, -_a + x), 2), (Poly(w2 + 1, w2, domain='ZZ'), Lambda(_a, -4), Lambda(_a, -_a + x), 1) ])
def test_apart_list(): assert apart_list(1) == 1 w0, w1, w2 = Symbol("w0"), Symbol("w1"), Symbol("w2") _a = Dummy("a") f = (-2 * x - 2 * x**2) / (3 * x**2 - 6 * x) assert (apart_list( f, x, dummies=numbered_symbols("w")) == (-1, Poly(Rational(2, 3), x), [ (Poly(w0 - 2, w0), Lambda(_a, 2), Lambda(_a, -_a + x), 1) ])) assert (apart_list(2 / (x**2 - 2), x, dummies=numbered_symbols("w")) == (1, Poly(0, x), [ (Poly(w0**2 - 2, w0), Lambda(_a, _a / 2), Lambda(_a, -_a + x), 1) ])) f = 36 / (x**5 - 2 * x**4 - 2 * x**3 + 4 * x**2 + x - 2) assert (apart_list(f, x, dummies=numbered_symbols("w")) == (1, Poly(0, x), [ (Poly(w0 - 2, w0), Lambda(_a, 4), Lambda(_a, -_a + x), 1), (Poly(w1**2 - 1, w1), Lambda(_a, -3 * _a - 6), Lambda(_a, -_a + x), 2), (Poly(w2 + 1, w2), Lambda(_a, -4), Lambda(_a, -_a + x), 1) ])) f = 1 / (2 * (x - 1)**2) assert (apart_list(f, x, dummies=numbered_symbols("w")) == (1, Poly(0, x), [ (Poly(2, w0), Lambda(_a, 0), Lambda(_a, x - _a), 1), (Poly(w1 - 1, w1), Lambda(_a, Rational(1, 2)), Lambda(_a, x - _a), 2), (Poly(1, w2), Lambda(_a, 0), Lambda(_a, x - _a), 1) ]))
def test_numbered_symbols(): s = numbered_symbols(cls=Dummy) assert isinstance(next(s), Dummy) assert next(numbered_symbols('C', start=1, exclude=[symbols('C1')])) == \ symbols('C2')
def test_filter_symbols(): s = numbered_symbols() filtered = filter_symbols(s, symbols("x0 x2 x3")) assert take(filtered, 3) == list(symbols("x1 x4 x5"))
def cse(exprs, symbols=None, optimizations=None, postprocess=None, order='canonical'): """ Perform common subexpression elimination on an expression. Parameters ========== exprs : list of diofant expressions, or a single diofant 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 The (preprocessor, postprocessor) pairs of external optimization functions. Optionally 'basic' can be passed for a set of predefined basic optimizations. Such 'basic' optimizations were used by default in old implementation, however they can be really slow on larger expressions. Now, no pre or post optimizations are made by default. 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 order : string, 'none' or 'canonical' The order by which Mul and Add arguments are processed. If set to 'canonical', arguments will be canonically ordered. If set to 'none', ordering will be faster but dependent on expressions hashes, thus machine dependent and variable. For large expressions where speed is a concern, use the setting order='none'. 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 diofant expressions The reduced expressions with all of the replacements above. Examples ======== >>> from diofant import cse, SparseMatrix >>> from diofant.abc import x, y, z, w >>> cse(((w + x + y + z)*(w + y + z))/(w + x)**3) ([(x0, y + z), (x1, w + x)], [(w + x0)*(x0 + x1)/x1**3]) Note that currently, y + z will not get substituted if -y - z is used. >>> cse(((w + x + y + z)*(w - y - z))/(w + x)**3) ([(x0, w + x)], [(w - y - z)*(x0 + y + z)/x0**3]) List of expressions with recursive substitutions: >>> m = SparseMatrix([x + y, x + y + z]) >>> cse([(x+y)**2, x + y + z, y + z, x + z + y, m]) ([(x0, x + y), (x1, x0 + z)], [x0**2, x1, y + z, x1, Matrix([ [x0], [x1]])]) Note: the type and mutability of input matrices is retained. >>> isinstance(_[1][-1], SparseMatrix) True """ from diofant.matrices import (MatrixBase, Matrix, ImmutableMatrix, SparseMatrix, ImmutableSparseMatrix) # Handle the case if just one expression was passed. if isinstance(exprs, (Basic, MatrixBase)): exprs = [exprs] copy = exprs temp = [] for e in exprs: if isinstance(e, (Matrix, ImmutableMatrix)): temp.append(Tuple(*e._mat)) elif isinstance(e, (SparseMatrix, ImmutableSparseMatrix)): temp.append(Tuple(*e._smat.items())) else: temp.append(e) exprs = temp del temp if optimizations is None: optimizations = list() elif optimizations == 'basic': optimizations = basic_optimizations # Preprocess the expressions to give us better optimization opportunities. reduced_exprs = [preprocess_for_cse(e, optimizations) for e in exprs] excluded_symbols = set().union( *[expr.atoms(Symbol) for expr in reduced_exprs]) 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) symbols = filter_symbols(symbols, excluded_symbols) # Find other optimization opportunities. opt_subs = opt_cse(reduced_exprs, order) # Main CSE algorithm. replacements, reduced_exprs = tree_cse(reduced_exprs, symbols, opt_subs, order) # Postprocess the expressions to return the expressions to canonical form. exprs = copy 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 ] # Get the matrices back for i, e in enumerate(exprs): if isinstance(e, (Matrix, ImmutableMatrix)): reduced_exprs[i] = Matrix(e.rows, e.cols, reduced_exprs[i]) if isinstance(e, ImmutableMatrix): reduced_exprs[i] = reduced_exprs[i].as_immutable() elif isinstance(e, (SparseMatrix, ImmutableSparseMatrix)): m = SparseMatrix(e.rows, e.cols, {}) for k, v in reduced_exprs[i]: m[k] = v if isinstance(e, ImmutableSparseMatrix): m = m.as_immutable() reduced_exprs[i] = m if postprocess is None: return replacements, reduced_exprs return postprocess(replacements, reduced_exprs)
def rsolve_hyper(coeffs, f, n, **hints): """ Given linear recurrence operator `\operatorname{L}` of order `k` with polynomial coefficients and inhomogeneous equation `\operatorname{L} y = f` we seek for all hypergeometric solutions over field `K` of characteristic zero. The inhomogeneous part can be either hypergeometric or a sum of a fixed number of pairwise dissimilar hypergeometric terms. The algorithm performs three basic steps: (1) Group together similar hypergeometric terms in the inhomogeneous part of `\operatorname{L} y = f`, and find particular solution using Abramov's algorithm. (2) Compute generating set of `\operatorname{L}` and find basis in it, so that all solutions are linearly independent. (3) Form final solution with the number of arbitrary constants equal to dimension of basis of `\operatorname{L}`. Term `a(n)` is hypergeometric if it is annihilated by first order linear difference equations with polynomial coefficients or, in simpler words, if consecutive term ratio is a rational function. The output of this procedure is a linear combination of fixed number of hypergeometric terms. However the underlying method can generate larger class of solutions - D'Alembertian terms. Note also that this method not only computes the kernel of the inhomogeneous equation, but also reduces in to a basis so that solutions generated by this procedure are linearly independent Examples ======== >>> from diofant.solvers import rsolve_hyper >>> from diofant.abc import x >>> rsolve_hyper([-1, -1, 1], 0, x) C0*(1/2 + sqrt(5)/2)**x + C1*(-sqrt(5)/2 + 1/2)**x >>> rsolve_hyper([-1, 1], 1 + x, x) C0 + x*(x + 1)/2 References ========== .. [1] M. Petkovšek, Hypergeometric solutions of linear recurrences with polynomial coefficients, J. Symbolic Computation, 14 (1992), 243-264. .. [2] M. Petkovšek, H. S. Wilf, D. Zeilberger, A = B, 1996. """ coeffs = list(map(sympify, coeffs)) f = sympify(f) r, kernel, symbols = len(coeffs) - 1, [], set() if not f.is_zero: if f.is_Add: similar = {} for g in f.expand().args: if not g.is_hypergeometric(n): return for h in similar.keys(): if hypersimilar(g, h, n): similar[h] += g break else: similar[g] = S.Zero inhomogeneous = [] for g, h in similar.items(): inhomogeneous.append(g + h) elif f.is_hypergeometric(n): inhomogeneous = [f] else: return for i, g in enumerate(inhomogeneous): coeff, polys = S.One, coeffs[:] denoms = [S.One] * (r + 1) s = hypersimp(g, n) for j in range(1, r + 1): coeff *= s.subs(n, n + j - 1) p, q = coeff.as_numer_denom() polys[j] *= p denoms[j] = q for j in range(0, r + 1): polys[j] *= Mul(*(denoms[:j] + denoms[j + 1:])) R = rsolve_ratio(polys, Mul(*denoms), n, symbols=True) if R is not None: R, syms = R if syms: R = R.subs(zip(syms, [0] * len(syms))) if R: inhomogeneous[i] *= R else: return result = Add(*inhomogeneous) result = simplify(result) else: result = S.Zero Z = Dummy('Z') p, q = coeffs[0], coeffs[r].subs(n, n - r + 1) p_factors = [z for z in roots(p, n).keys()] q_factors = [z for z in roots(q, n).keys()] factors = [(S.One, S.One)] for p in p_factors: for q in q_factors: if p.is_integer and q.is_integer and p <= q: continue else: factors += [(n - p, n - q)] p = [(n - p, S.One) for p in p_factors] q = [(S.One, n - q) for q in q_factors] factors = p + factors + q for A, B in factors: polys, degrees = [], [] D = A * B.subs(n, n + r - 1) for i in range(0, r + 1): a = Mul(*[A.subs(n, n + j) for j in range(0, i)]) b = Mul(*[B.subs(n, n + j) for j in range(i, r)]) poly = quo(coeffs[i] * a * b, D, n) polys.append(poly.as_poly(n)) if not poly.is_zero: degrees.append(polys[i].degree()) d, poly = max(degrees), S.Zero for i in range(0, r + 1): coeff = polys[i].nth(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).keys(): if z.is_zero: continue (C, s) = rsolve_poly([polys[i] * z**i for i in range(r + 1)], 0, n, symbols=True) if C is not None and C is not S.Zero: symbols |= set(s) ratio = z * A * C.subs(n, n + 1) / B / C ratio = simplify(ratio) skip = max([-1] + [ v for v in roots(Mul(*ratio.as_numer_denom()), n).keys() if v.is_Integer ]) + 1 K = product(ratio, (n, skip, n - 1)) if K.has(factorial, FallingFactorial, RisingFactorial): K = simplify(K) if casoratian(kernel + [K], n, zero=False) != 0: kernel.append(K) kernel.sort(key=default_sort_key) sk = list(zip(numbered_symbols('C'), kernel)) for C, ker in sk: result += C * ker if hints.get('symbols', False): symbols |= {s for s, k in sk} return result, list(symbols) else: return result
def __init__(self, args, expr, print_lambda=False, use_evalf=False, float_wrap_evalf=False, complex_wrap_evalf=False, use_np=False, use_python_math=False, use_python_cmath=False, use_interval=False): self.print_lambda = print_lambda self.use_evalf = use_evalf self.float_wrap_evalf = float_wrap_evalf self.complex_wrap_evalf = complex_wrap_evalf self.use_np = use_np self.use_python_math = use_python_math self.use_python_cmath = use_python_cmath self.use_interval = use_interval # Constructing the argument string # - check if not all([isinstance(a, (Dummy, Symbol)) for a in args]): raise ValueError('The arguments must be Symbols.') # - use numbered symbols syms = numbered_symbols(exclude=expr.free_symbols) newargs = [next(syms) for i in args] expr = expr.xreplace(dict(zip(args, newargs))) argstr = ', '.join([str(a) for a in newargs]) del syms, newargs, args # Constructing the translation dictionaries and making the translation self.dict_str = self.get_dict_str() self.dict_fun = self.get_dict_fun() exprstr = str(expr) newexpr = self.tree2str_translate(self.str2tree(exprstr)) # Constructing the namespaces namespace = {} namespace.update(self.diofant_atoms_namespace(expr)) namespace.update(self.diofant_expression_namespace(expr)) # XXX Workaround # Ugly workaround because Pow(a,Half) prints as sqrt(a) # and diofant_expression_namespace can not catch it. from diofant import sqrt namespace.update({'sqrt': sqrt}) namespace.update({'Eq': lambda x, y: x == y}) # End workaround. if use_python_math: namespace.update({'math': __import__('math')}) if use_python_cmath: namespace.update({'cmath': __import__('cmath')}) if use_np: try: namespace.update({'np': __import__('numpy')}) except ImportError: raise ImportError( 'experimental_lambdify failed to import numpy.') if use_interval: namespace.update({ 'imath': __import__('diofant.plotting.intervalmath', fromlist=['intervalmath']) }) namespace.update({'math': __import__('math')}) # Construct the lambda if self.print_lambda: print(newexpr) eval_str = 'lambda %s : ( %s )' % (argstr, newexpr) exec("MYNEWLAMBDA = %s" % eval_str, namespace) self.lambda_func = namespace['MYNEWLAMBDA']
def test_filter_symbols(): s = numbered_symbols() filtered = filter_symbols(s, symbols("x0 x2 x3")) assert list(itertools.islice(filtered, 3)) == list(symbols("x1 x4 x5")) assert set(filter_symbols((), set())) == set()
def test_filter_symbols(): s = numbered_symbols() filtered = filter_symbols(s, symbols("x0 x2 x3")) assert take(filtered, 3) == list(symbols("x1 x4 x5")) assert set(filter_symbols((), set())) == set()