def _add_splines(c, b1, d, b2): """Construct c*b1 + d*b2.""" if b1 == S.Zero or c == S.Zero: rv = piecewise_fold(d * b2) elif b2 == S.Zero or d == S.Zero: rv = piecewise_fold(c * b1) else: new_args = [] n_intervals = len(b1.args) if n_intervals != len(b2.args): raise ValueError("Args of b1 and b2 are not equal") new_args.append((c * b1.args[0].expr, b1.args[0].cond)) for i in range(1, n_intervals - 1): new_args.append((c * b1.args[i].expr + d * b2.args[i - 1].expr, b1.args[i].cond)) new_args.append((d * b2.args[-2].expr, b2.args[-2].cond)) new_args.append(b2.args[-1]) rv = Piecewise(*new_args) return rv.expand()
def test_deltasummation_mul_add_x_kd_add_y_kd(): assert ds((x + KD(i, k)) * (y + KD(i, j)), (j, 1, 3)) == piecewise_fold( Piecewise((KD(i, k) + x, And(Integer(1) <= i, i <= 3)), (0, True)) + 3 * (KD(i, k) + x) * y) assert ds((x + KD(i, k)) * (y + KD(i, j)), (j, 1, 1)) == piecewise_fold( Piecewise((KD(i, k) + x, Eq(i, 1)), (0, True)) + (KD(i, k) + x) * y) assert ds((x + KD(i, k)) * (y + KD(i, j)), (j, 2, 2)) == piecewise_fold( Piecewise((KD(i, k) + x, Eq(i, 2)), (0, True)) + (KD(i, k) + x) * y) assert ds((x + KD(i, k)) * (y + KD(i, j)), (j, 3, 3)) == piecewise_fold( Piecewise((KD(i, k) + x, Eq(i, 3)), (0, True)) + (KD(i, k) + x) * y) assert ds((x + KD(i, k)) * (y + KD(i, j)), (j, 1, k)) == piecewise_fold( Piecewise((KD(i, k) + x, And(Integer(1) <= i, i <= k)), (0, True)) + k * (KD(i, k) + x) * y) assert ds((x + KD(i, k)) * (y + KD(i, j)), (j, k, 3)) == piecewise_fold( Piecewise((KD(i, k) + x, And(k <= i, i <= 3)), (0, True)) + (4 - k) * (KD(i, k) + x) * y) assert ds((x + KD(i, k)) * (y + KD(i, j)), (j, k, l)) == piecewise_fold( Piecewise((KD(i, k) + x, And(k <= i, i <= l)), (0, True)) + (l - k + 1) * (KD(i, k) + x) * y)
def test_deltasummation_mul_add_x_kd_add_y_kd(): assert ds((x + Kd(i, k))*(y + Kd(i, j)), (j, 1, 3)) == piecewise_fold( Piecewise((Kd(i, k) + x, And(Integer(1) <= i, i <= 3)), (0, True)) + 3*(Kd(i, k) + x)*y) assert ds((x + Kd(i, k))*(y + Kd(i, j)), (j, 1, 1)) == piecewise_fold( Piecewise((Kd(i, k) + x, Eq(i, 1)), (0, True)) + (Kd(i, k) + x)*y) assert ds((x + Kd(i, k))*(y + Kd(i, j)), (j, 2, 2)) == piecewise_fold( Piecewise((Kd(i, k) + x, Eq(i, 2)), (0, True)) + (Kd(i, k) + x)*y) assert ds((x + Kd(i, k))*(y + Kd(i, j)), (j, 3, 3)) == piecewise_fold( Piecewise((Kd(i, k) + x, Eq(i, 3)), (0, True)) + (Kd(i, k) + x)*y) assert ds((x + Kd(i, k))*(y + Kd(i, j)), (j, 1, k)) == piecewise_fold( Piecewise((Kd(i, k) + x, And(Integer(1) <= i, i <= k)), (0, True)) + k*(Kd(i, k) + x)*y) assert ds((x + Kd(i, k))*(y + Kd(i, j)), (j, k, 3)) == piecewise_fold( Piecewise((Kd(i, k) + x, And(k <= i, i <= 3)), (0, True)) + (4 - k)*(Kd(i, k) + x)*y) assert ds((x + Kd(i, k))*(y + Kd(i, j)), (j, k, l)) == piecewise_fold( Piecewise((Kd(i, k) + x, And(k <= i, i <= l)), (0, True)) + (l - k + 1)*(Kd(i, k) + x)*y)
def test_deltasummation_mul_add_x_y_add_kd_kd(): assert ds((x + y)*(Kd(i, k) + Kd(j, k)), (k, 1, 3)) == piecewise_fold( Piecewise((x + y, And(Integer(1) <= i, i <= 3)), (0, True)) + Piecewise((x + y, And(Integer(1) <= j, j <= 3)), (0, True))) assert ds((x + y)*(Kd(i, k) + Kd(j, k)), (k, 1, 1)) == piecewise_fold( Piecewise((x + y, Eq(i, 1)), (0, True)) + Piecewise((x + y, Eq(j, 1)), (0, True))) assert ds((x + y)*(Kd(i, k) + Kd(j, k)), (k, 2, 2)) == piecewise_fold( Piecewise((x + y, Eq(i, 2)), (0, True)) + Piecewise((x + y, Eq(j, 2)), (0, True))) assert ds((x + y)*(Kd(i, k) + Kd(j, k)), (k, 3, 3)) == piecewise_fold( Piecewise((x + y, Eq(i, 3)), (0, True)) + Piecewise((x + y, Eq(j, 3)), (0, True))) assert ds((x + y)*(Kd(i, k) + Kd(j, k)), (k, 1, l)) == piecewise_fold( Piecewise((x + y, And(Integer(1) <= i, i <= l)), (0, True)) + Piecewise((x + y, And(Integer(1) <= j, j <= l)), (0, True))) assert ds((x + y)*(Kd(i, k) + Kd(j, k)), (k, l, 3)) == piecewise_fold( Piecewise((x + y, And(l <= i, i <= 3)), (0, True)) + Piecewise((x + y, And(l <= j, j <= 3)), (0, True))) assert ds((x + y)*(Kd(i, k) + Kd(j, k)), (k, l, m)) == piecewise_fold( Piecewise((x + y, And(l <= i, i <= m)), (0, True)) + Piecewise((x + y, And(l <= j, j <= m)), (0, True)))
def test_deltasummation_mul_add_x_y_add_kd_kd(): assert ds((x + y) * (KD(i, k) + KD(j, k)), (k, 1, 3)) == piecewise_fold( Piecewise((x + y, And(Integer(1) <= i, i <= 3)), (0, True)) + Piecewise((x + y, And(Integer(1) <= j, j <= 3)), (0, True))) assert ds((x + y) * (KD(i, k) + KD(j, k)), (k, 1, 1)) == piecewise_fold( Piecewise((x + y, Eq(i, 1)), (0, True)) + Piecewise((x + y, Eq(j, 1)), (0, True))) assert ds((x + y) * (KD(i, k) + KD(j, k)), (k, 2, 2)) == piecewise_fold( Piecewise((x + y, Eq(i, 2)), (0, True)) + Piecewise((x + y, Eq(j, 2)), (0, True))) assert ds((x + y) * (KD(i, k) + KD(j, k)), (k, 3, 3)) == piecewise_fold( Piecewise((x + y, Eq(i, 3)), (0, True)) + Piecewise((x + y, Eq(j, 3)), (0, True))) assert ds((x + y) * (KD(i, k) + KD(j, k)), (k, 1, l)) == piecewise_fold( Piecewise((x + y, And(Integer(1) <= i, i <= l)), (0, True)) + Piecewise((x + y, And(Integer(1) <= j, j <= l)), (0, True))) assert ds((x + y) * (KD(i, k) + KD(j, k)), (k, l, 3)) == piecewise_fold( Piecewise((x + y, And(l <= i, i <= 3)), (0, True)) + Piecewise((x + y, And(l <= j, j <= 3)), (0, True))) assert ds((x + y) * (KD(i, k) + KD(j, k)), (k, l, m)) == piecewise_fold( Piecewise((x + y, And(l <= i, i <= m)), (0, True)) + Piecewise((x + y, And(l <= j, j <= m)), (0, True)))
def simplify(expr, ratio=1.7, measure=count_ops, fu=False): """ Simplifies the given expression. Simplification is not a well defined term and the exact strategies this function tries can change in the future versions of Diofant. If your algorithm relies on "simplification" (whatever it is), try to determine what you need exactly - is it powsimp()?, radsimp()?, together()?, logcombine()?, or something else? And use this particular function directly, because those are well defined and thus your algorithm will be robust. Nonetheless, especially for interactive use, or when you don't know anything about the structure of the expression, simplify() tries to apply intelligent heuristics to make the input expression "simpler". For example: >>> from diofant import simplify, cos, sin >>> from diofant.abc import x, y >>> a = (x + x**2)/(x*sin(y)**2 + x*cos(y)**2) >>> a (x**2 + x)/(x*sin(y)**2 + x*cos(y)**2) >>> simplify(a) x + 1 Note that we could have obtained the same result by using specific simplification functions: >>> from diofant import trigsimp, cancel >>> trigsimp(a) (x**2 + x)/x >>> cancel(_) x + 1 In some cases, applying :func:`simplify` may actually result in some more complicated expression. The default ``ratio=1.7`` prevents more extreme cases: if (result length)/(input length) > ratio, then input is returned unmodified. The ``measure`` parameter lets you specify the function used to determine how complex an expression is. The function should take a single argument as an expression and return a number such that if expression ``a`` is more complex than expression ``b``, then ``measure(a) > measure(b)``. The default measure function is :func:`~diofant.core.function.count_ops`, which returns the total number of operations in the expression. For example, if ``ratio=1``, ``simplify`` output can't be longer than input. :: >>> from diofant import sqrt, simplify, count_ops, oo >>> root = 1/(sqrt(2)+3) Since ``simplify(root)`` would result in a slightly longer expression, root is returned unchanged instead:: >>> simplify(root, ratio=1) == root True If ``ratio=oo``, simplify will be applied anyway:: >>> count_ops(simplify(root, ratio=oo)) > count_ops(root) True Note that the shortest expression is not necessary the simplest, so setting ``ratio`` to 1 may not be a good idea. Heuristically, the default value ``ratio=1.7`` seems like a reasonable choice. You can easily define your own measure function based on what you feel should represent the "size" or "complexity" of the input expression. Note that some choices, such as ``lambda expr: len(str(expr))`` may appear to be good metrics, but have other problems (in this case, the measure function may slow down simplify too much for very large expressions). If you don't know what a good metric would be, the default, ``count_ops``, is a good one. For example: >>> from diofant import symbols, log >>> a, b = symbols('a b', positive=True) >>> g = log(a) + log(b) + log(a)*log(1/b) >>> h = simplify(g) >>> h log(a*b**(-log(a) + 1)) >>> count_ops(g) 8 >>> count_ops(h) 5 So you can see that ``h`` is simpler than ``g`` using the count_ops metric. However, we may not like how ``simplify`` (in this case, using ``logcombine``) has created the ``b**(log(1/a) + 1)`` term. A simple way to reduce this would be to give more weight to powers as operations in ``count_ops``. We can do this by using the ``visual=True`` option: >>> print(count_ops(g, visual=True)) 2*ADD + DIV + 4*LOG + MUL >>> print(count_ops(h, visual=True)) 2*LOG + MUL + POW + SUB >>> from diofant import Symbol, S >>> def my_measure(expr): ... POW = Symbol('POW') ... # Discourage powers by giving POW a weight of 10 ... count = count_ops(expr, visual=True).subs(POW, 10) ... # Every other operation gets a weight of 1 (the default) ... count = count.replace(Symbol, type(S.One)) ... return count >>> my_measure(g) 8 >>> my_measure(h) 14 >>> 15./8 > 1.7 # 1.7 is the default ratio True >>> simplify(g, measure=my_measure) -log(a)*log(b) + log(a) + log(b) Note that because ``simplify()`` internally tries many different simplification strategies and then compares them using the measure function, we get a completely different result that is still different from the input expression by doing this. """ expr = sympify(expr) try: return expr._eval_simplify(ratio=ratio, measure=measure) except AttributeError: pass original_expr = expr = signsimp(expr) from diofant.simplify.hyperexpand import hyperexpand from diofant.functions.special.bessel import BesselBase from diofant import Sum, Product if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack return expr if not isinstance(expr, (Add, Mul, Pow, exp_polar)): return expr.func(*[ simplify(x, ratio=ratio, measure=measure, fu=fu) for x in expr.args ]) # TODO: Apply different strategies, considering expression pattern: # is it a purely rational function? Is there any trigonometric function?... # See also https://github.com/sympy/sympy/pull/185. def shorter(*choices): '''Return the choice that has the fewest ops. In case of a tie, the expression listed first is selected.''' if not has_variety(choices): return choices[0] return min(choices, key=measure) expr = bottom_up(expr, lambda w: w.normal()) expr = Mul(*powsimp(expr).as_content_primitive()) _e = cancel(expr) expr1 = shorter(_e, _mexpand(_e).cancel()) # issue 6829 expr2 = shorter(together(expr, deep=True), together(expr1, deep=True)) if ratio is S.Infinity: expr = expr2 else: expr = shorter(expr2, expr1, expr) if not isinstance(expr, Basic): # XXX: temporary hack return expr expr = factor_terms(expr, sign=False) # hyperexpand automatically only works on hypergeometric terms expr = hyperexpand(expr) expr = piecewise_fold(expr) if expr.has(BesselBase): expr = besselsimp(expr) if expr.has(TrigonometricFunction) and not fu or expr.has( HyperbolicFunction): expr = trigsimp(expr, deep=True) if expr.has(log): expr = shorter(expand_log(expr, deep=True), logcombine(expr)) if expr.has(CombinatorialFunction, gamma): expr = combsimp(expr) if expr.has(Sum): expr = sum_simplify(expr) if expr.has(Product): expr = product_simplify(expr) short = shorter(powsimp(expr, combine='exp', deep=True), powsimp(expr), expr) short = shorter(short, factor_terms(short), expand_power_exp(expand_mul(short))) if (short.has(TrigonometricFunction, HyperbolicFunction, exp_polar) or any(a.base is S.Exp1 for a in short.atoms(Pow))): short = exptrigsimp(short, simplify=False) # get rid of hollow 2-arg Mul factorization hollow_mul = Transform( lambda x: Mul(*x.args), lambda x: x.is_Mul and len(x.args) == 2 and x. args[0].is_Number and x.args[1].is_Add and x.is_commutative) expr = short.xreplace(hollow_mul) numer, denom = expr.as_numer_denom() if denom.is_Add: n, d = fraction(radsimp(1 / denom, symbolic=False, max_terms=1)) if n is not S.One: expr = (numer * n).expand() / d if expr.could_extract_minus_sign(): n, d = fraction(expr) if d != 0: expr = signsimp(-n / (-d)) if measure(expr) > ratio * measure(original_expr): expr = original_expr return expr
def deltasummation(f, limit, no_piecewise=False): """Handle summations containing a KroneckerDelta. The idea for summation is the following: - If we are dealing with a KroneckerDelta expression, i.e. KroneckerDelta(g(x), j), we try to simplify it. If we could simplify it, then we sum the resulting expression. We already know we can sum a simplified expression, because only simple KroneckerDelta expressions are involved. If we couldn't simplify it, there are two cases: 1) The expression is a simple expression: we return the summation, taking care if we are dealing with a Derivative or with a proper KroneckerDelta. 2) The expression is not simple (i.e. KroneckerDelta(cos(x))): we can do nothing at all. - If the expr is a multiplication expr having a KroneckerDelta term: First we expand it. If the expansion did work, then we try to sum the expansion. If not, we try to extract a simple KroneckerDelta term, then we have two cases: 1) We have a simple KroneckerDelta term, so we return the summation. 2) We didn't have a simple term, but we do have an expression with simplified KroneckerDelta terms, so we sum this expression. Examples ======== >>> from diofant import oo, symbols >>> from diofant.abc import k >>> i, j = symbols('i, j', integer=True, finite=True) >>> from diofant import KroneckerDelta, Piecewise >>> deltasummation(KroneckerDelta(i, k), (k, -oo, oo)) 1 >>> deltasummation(KroneckerDelta(i, k), (k, 0, oo)) Piecewise((1, 0 <= i), (0, true)) >>> deltasummation(KroneckerDelta(i, k), (k, 1, 3)) Piecewise((1, And(1 <= i, i <= 3)), (0, true)) >>> deltasummation(k*KroneckerDelta(i, j)*KroneckerDelta(j, k), (k, -oo, oo)) j*KroneckerDelta(i, j) >>> deltasummation(j*KroneckerDelta(i, j), (j, -oo, oo)) i >>> deltasummation(i*KroneckerDelta(i, j), (i, -oo, oo)) j See Also ======== deltaproduct diofant.functions.special.tensor_functions.KroneckerDelta diofant.concrete.sums.summation """ from diofant.concrete.summations import summation from diofant.solvers import solve if ((limit[2] - limit[1]) < 0) is S.true: return S.Zero if not f.has(KroneckerDelta): return summation(f, limit) x = limit[0] g = _expand_delta(f, x) if g.is_Add: return piecewise_fold( g.func(*[deltasummation(h, limit, no_piecewise) for h in g.args])) # try to extract a simple KroneckerDelta term delta, expr = _extract_delta(g, x) if not delta: return summation(f, limit) solns = solve(delta.args[0] - delta.args[1], x) if len(solns) == 0: return S.Zero elif len(solns) != 1: return Sum(f, limit) value = solns[0] if no_piecewise: return expr.subs(x, value) return Piecewise( (expr.subs(x, value), Interval(*limit[1:3]).as_relational(value)), (S.Zero, True))