| from sympy.core import Function, S, Mul, Pow, Add |
| from sympy.core.sorting import ordered, default_sort_key |
| from sympy.core.function import expand_func |
| from sympy.core.symbol import Dummy |
| from sympy.functions import gamma, sqrt, sin |
| from sympy.polys import factor, cancel |
| from sympy.utilities.iterables import sift, uniq |
|
|
|
|
| def gammasimp(expr): |
| r""" |
| Simplify expressions with gamma functions. |
| |
| Explanation |
| =========== |
| |
| This function takes as input an expression containing gamma |
| functions or functions that can be rewritten in terms of gamma |
| functions and tries to minimize the number of those functions and |
| reduce the size of their arguments. |
| |
| The algorithm works by rewriting all gamma functions as expressions |
| involving rising factorials (Pochhammer symbols) and applies |
| recurrence relations and other transformations applicable to rising |
| factorials, to reduce their arguments, possibly letting the resulting |
| rising factorial to cancel. Rising factorials with the second argument |
| being an integer are expanded into polynomial forms and finally all |
| other rising factorial are rewritten in terms of gamma functions. |
| |
| Then the following two steps are performed. |
| |
| 1. Reduce the number of gammas by applying the reflection theorem |
| gamma(x)*gamma(1-x) == pi/sin(pi*x). |
| 2. Reduce the number of gammas by applying the multiplication theorem |
| gamma(x)*gamma(x+1/n)*...*gamma(x+(n-1)/n) == C*gamma(n*x). |
| |
| It then reduces the number of prefactors by absorbing them into gammas |
| where possible and expands gammas with rational argument. |
| |
| All transformation rules can be found (or were derived from) here: |
| |
| .. [1] https://functions.wolfram.com/GammaBetaErf/Pochhammer/17/01/02/ |
| .. [2] https://functions.wolfram.com/GammaBetaErf/Pochhammer/27/01/0005/ |
| |
| Examples |
| ======== |
| |
| >>> from sympy.simplify import gammasimp |
| >>> from sympy import gamma, Symbol |
| >>> from sympy.abc import x |
| >>> n = Symbol('n', integer = True) |
| |
| >>> gammasimp(gamma(x)/gamma(x - 3)) |
| (x - 3)*(x - 2)*(x - 1) |
| >>> gammasimp(gamma(n + 3)) |
| gamma(n + 3) |
| |
| """ |
|
|
| expr = expr.rewrite(gamma) |
|
|
| |
| |
| |
| f = expr.atoms(Function) |
| |
| gammas = {i for i in f if isinstance(i, gamma)} |
| if not gammas: |
| return expr |
| f -= gammas |
| |
| f = f & expr.as_dummy().atoms(Function) |
| if f: |
| dum, fun, simp = zip(*[ |
| (Dummy(), fi, fi.func(*[ |
| _gammasimp(a, as_comb=False) for a in fi.args])) |
| for fi in ordered(f)]) |
| d = expr.xreplace(dict(zip(fun, dum))) |
| return _gammasimp(d, as_comb=False).xreplace(dict(zip(dum, simp))) |
|
|
| return _gammasimp(expr, as_comb=False) |
|
|
|
|
| def _gammasimp(expr, as_comb): |
| """ |
| Helper function for gammasimp and combsimp. |
| |
| Explanation |
| =========== |
| |
| Simplifies expressions written in terms of gamma function. If |
| as_comb is True, it tries to preserve integer arguments. See |
| docstring of gammasimp for more information. This was part of |
| combsimp() in combsimp.py. |
| """ |
| expr = expr.replace(gamma, |
| lambda n: _rf(1, (n - 1).expand())) |
|
|
| if as_comb: |
| expr = expr.replace(_rf, |
| lambda a, b: gamma(b + 1)) |
| else: |
| expr = expr.replace(_rf, |
| lambda a, b: gamma(a + b)/gamma(a)) |
|
|
| def rule_gamma(expr, level=0): |
| """ Simplify products of gamma functions further. """ |
|
|
| if expr.is_Atom: |
| return expr |
|
|
| def gamma_rat(x): |
| |
| was = x.count(gamma) |
| xx = x.replace(gamma, lambda n: _rf(1, (n - 1).expand() |
| ).replace(_rf, lambda a, b: gamma(a + b)/gamma(a))) |
| if xx.count(gamma) < was: |
| x = xx |
| return x |
|
|
| def gamma_factor(x): |
| |
| if isinstance(x, gamma): |
| return True |
| if x.is_Add or x.is_Mul: |
| return any(gamma_factor(xi) for xi in x.args) |
| if x.is_Pow and (x.exp.is_integer or x.base.is_positive): |
| return gamma_factor(x.base) |
| return False |
|
|
| |
| if level == 0: |
| expr = expr.func(*[rule_gamma(x, level + 1) for x in expr.args]) |
| level += 1 |
|
|
| if not expr.is_Mul: |
| return expr |
|
|
| |
| if level == 1: |
| args, nc = expr.args_cnc() |
| if not args: |
| return expr |
| if nc: |
| return rule_gamma(Mul._from_args(args), level + 1)*Mul._from_args(nc) |
| level += 1 |
|
|
| |
| if level == 2: |
| T, F = sift(expr.args, gamma_factor, binary=True) |
| gamma_ind = Mul(*F) |
| d = Mul(*T) |
|
|
| nd, dd = d.as_numer_denom() |
| for ipass in range(2): |
| args = list(ordered(Mul.make_args(nd))) |
| for i, ni in enumerate(args): |
| if ni.is_Add: |
| ni, dd = Add(*[ |
| rule_gamma(gamma_rat(a/dd), level + 1) for a in ni.args] |
| ).as_numer_denom() |
| args[i] = ni |
| if not dd.has(gamma): |
| break |
| nd = Mul(*args) |
| if ipass == 0 and not gamma_factor(nd): |
| break |
| nd, dd = dd, nd |
| expr = gamma_ind*nd/dd |
| if not (expr.is_Mul and (gamma_factor(dd) or gamma_factor(nd))): |
| return expr |
| level += 1 |
|
|
| |
| if level == 3: |
| while True: |
| was = expr |
| expr = rule_gamma(expr, 4) |
| if expr == was: |
| return expr |
|
|
| numer_gammas = [] |
| denom_gammas = [] |
| numer_others = [] |
| denom_others = [] |
| def explicate(p): |
| if p is S.One: |
| return None, [] |
| b, e = p.as_base_exp() |
| if e.is_Integer: |
| if isinstance(b, gamma): |
| return True, [b.args[0]]*e |
| else: |
| return False, [b]*e |
| else: |
| return False, [p] |
|
|
| newargs = list(ordered(expr.args)) |
| while newargs: |
| n, d = newargs.pop().as_numer_denom() |
| isg, l = explicate(n) |
| if isg: |
| numer_gammas.extend(l) |
| elif isg is False: |
| numer_others.extend(l) |
| isg, l = explicate(d) |
| if isg: |
| denom_gammas.extend(l) |
| elif isg is False: |
| denom_others.extend(l) |
|
|
| |
|
|
| if not as_comb: |
| |
| |
| for gammas, numer, denom in [( |
| numer_gammas, numer_others, denom_others), |
| (denom_gammas, denom_others, numer_others)]: |
| new = [] |
| while gammas: |
| g1 = gammas.pop() |
| if g1.is_integer: |
| new.append(g1) |
| continue |
| for i, g2 in enumerate(gammas): |
| n = g1 + g2 - 1 |
| if not n.is_Integer: |
| continue |
| numer.append(S.Pi) |
| denom.append(sin(S.Pi*g1)) |
| gammas.pop(i) |
| if n > 0: |
| numer.extend(1 - g1 + k for k in range(n)) |
| elif n < 0: |
| denom.extend(-g1 - k for k in range(-n)) |
| break |
| else: |
| new.append(g1) |
| |
| gammas[:] = new |
|
|
| |
| |
| |
| |
| |
| for ng, dg, no, do in [(numer_gammas, denom_gammas, numer_others, |
| denom_others), |
| (denom_gammas, numer_gammas, denom_others, |
| numer_others)]: |
|
|
| while True: |
| for x in ng: |
| for y in dg: |
| n = x - 2*y |
| if n.is_Integer: |
| break |
| else: |
| continue |
| break |
| else: |
| break |
| ng.remove(x) |
| dg.remove(y) |
| if n > 0: |
| no.extend(2*y + k for k in range(n)) |
| elif n < 0: |
| do.extend(2*y - 1 - k for k in range(-n)) |
| ng.append(y + S.Half) |
| no.append(2**(2*y - 1)) |
| do.append(sqrt(S.Pi)) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| def _run(coeffs): |
| |
| |
| u = list(uniq(coeffs)) |
| for i in range(len(u)): |
| dj = ([((u[j] - u[i]) % 1, j) for j in range(i + 1, len(u))]) |
| for one, j in dj: |
| if one.p == 1 and one.q != 1: |
| n = one.q |
| got = [i] |
| get = list(range(1, n)) |
| for d, j in dj: |
| m = n*d |
| if m.is_Integer and m in get: |
| get.remove(m) |
| got.append(j) |
| if not get: |
| break |
| else: |
| continue |
| for i, j in enumerate(got): |
| c = u[j] |
| coeffs.remove(c) |
| got[i] = c |
| return one.q, got[0], got[1:] |
|
|
| def _mult_thm(gammas, numer, denom): |
| |
| |
|
|
| |
| rats = {} |
| for g in gammas: |
| c, resid = g.as_coeff_Add() |
| rats.setdefault(resid, []).append(c) |
|
|
| |
| keys = sorted(rats, key=default_sort_key) |
| for resid in keys: |
| coeffs = sorted(rats[resid]) |
| new = [] |
| while True: |
| run = _run(coeffs) |
| if run is None: |
| break |
|
|
| |
| |
| |
| |
| |
|
|
| n, ui, other = run |
|
|
| |
| for u in other: |
| con = resid + u - 1 |
| for k in range(int(u - ui)): |
| numer.append(con - k) |
|
|
| con = n*(resid + ui) |
|
|
| |
| numer.append((2*S.Pi)**(S(n - 1)/2)* |
| n**(S.Half - con)) |
| |
| new.append(con) |
|
|
| |
| rats[resid] = [resid + c for c in coeffs] + new |
|
|
| |
| g = [] |
| for resid in keys: |
| g += rats[resid] |
| |
| gammas[:] = g |
|
|
| for l, numer, denom in [(numer_gammas, numer_others, denom_others), |
| (denom_gammas, denom_others, numer_others)]: |
| _mult_thm(l, numer, denom) |
|
|
| |
|
|
| if level >= 2: |
| |
| |
| |
| |
| def find_fuzzy(l, x): |
| if not l: |
| return |
| S1, T1 = compute_ST(x) |
| for y in l: |
| S2, T2 = inv[y] |
| if T1 != T2 or (not S1.intersection(S2) and |
| (S1 != set() or S2 != set())): |
| continue |
| |
| |
| a = len(cancel(x/y).free_symbols) |
| b = len(x.free_symbols) |
| c = len(y.free_symbols) |
| |
| if a == 0 and (b > 0 or c > 0): |
| return y |
|
|
| |
| |
| |
| |
| |
| |
| inv = {} |
|
|
| def compute_ST(expr): |
| if expr in inv: |
| return inv[expr] |
| return (expr.free_symbols, expr.atoms(Function).union( |
| {e.exp for e in expr.atoms(Pow)})) |
|
|
| def update_ST(expr): |
| inv[expr] = compute_ST(expr) |
| for expr in numer_gammas + denom_gammas + numer_others + denom_others: |
| update_ST(expr) |
|
|
| for gammas, numer, denom in [( |
| numer_gammas, numer_others, denom_others), |
| (denom_gammas, denom_others, numer_others)]: |
| new = [] |
| while gammas: |
| g = gammas.pop() |
| cont = True |
| while cont: |
| cont = False |
| y = find_fuzzy(numer, g) |
| if y is not None: |
| numer.remove(y) |
| if y != g: |
| numer.append(y/g) |
| update_ST(y/g) |
| g += 1 |
| cont = True |
| y = find_fuzzy(denom, g - 1) |
| if y is not None: |
| denom.remove(y) |
| if y != g - 1: |
| numer.append((g - 1)/y) |
| update_ST((g - 1)/y) |
| g -= 1 |
| cont = True |
| new.append(g) |
| |
| gammas[:] = new |
|
|
| |
|
|
| return Mul(*[gamma(g) for g in numer_gammas]) \ |
| / Mul(*[gamma(g) for g in denom_gammas]) \ |
| * Mul(*numer_others) / Mul(*denom_others) |
|
|
| was = factor(expr) |
| |
| expr = rule_gamma(was) |
| if expr != was: |
| expr = factor(expr) |
|
|
| expr = expr.replace(gamma, |
| lambda n: expand_func(gamma(n)) if n.is_Rational else gamma(n)) |
|
|
| return expr |
|
|
|
|
| class _rf(Function): |
| @classmethod |
| def eval(cls, a, b): |
| if b.is_Integer: |
| if not b: |
| return S.One |
|
|
| n = int(b) |
|
|
| if n > 0: |
| return Mul(*[a + i for i in range(n)]) |
| elif n < 0: |
| return 1/Mul(*[a - i for i in range(1, -n + 1)]) |
| else: |
| if b.is_Add: |
| c, _b = b.as_coeff_Add() |
|
|
| if c.is_Integer: |
| if c > 0: |
| return _rf(a, _b)*_rf(a + _b, c) |
| elif c < 0: |
| return _rf(a, _b)/_rf(a + _b + c, -c) |
|
|
| if a.is_Add: |
| c, _a = a.as_coeff_Add() |
|
|
| if c.is_Integer: |
| if c > 0: |
| return _rf(_a, b)*_rf(_a + b, c)/_rf(_a, c) |
| elif c < 0: |
| return _rf(_a, b)*_rf(_a + c, -c)/_rf(_a + b + c, -c) |
|
|