def test_partial_simp(): # First test that hypergeometric function formulae work. a, b, c, d, e = map(lambda _: randcplx(), range(5)) for func in [Hyper_Function([a, b, c], [d, e]), Hyper_Function([], [a, b, c, d, e])]: f = build_hypergeometric_formula(func) z = f.z assert f.closed_form == func(z) deriv1 = f.B.diff(z)*z deriv2 = f.M*f.B for func1, func2 in zip(deriv1, deriv2): assert tn(func1, func2, z) # Now test that formulae are partially simplified. from sympy.abc import a, b, z assert hyperexpand(hyper([3, a], [1, b], z)) == \ (-a*b/2 + a*z/2 + 2*a)*hyper([a + 1], [b], z) \ + (a*b/2 - 2*a + 1)*hyper([a], [b], z) assert tn( hyperexpand(hyper([3, d], [1, e], z)), hyper([3, d], [1, e], z), z) assert hyperexpand(hyper([3], [1, a, b], z)) == \ hyper((), (a, b), z) \ + z*hyper((), (a + 1, b), z)/(2*a) \ - z*(b - 4)*hyper((), (a + 1, b + 1), z)/(2*a*b) assert tn( hyperexpand(hyper([3], [1, d, e], z)), hyper([3], [1, d, e], z), z)
def test_polynomial(): from sympy import oo assert hyperexpand(hyper([], [-1], z)) == oo assert hyperexpand(hyper([-2], [-1], z)) == oo assert hyperexpand(hyper([0, 0], [-1], z)) == 1 assert can_do([-5, -2, randcplx(), randcplx()], [-10, randcplx()]) assert hyperexpand(hyper((-1, 1), (-2,), z)) == 1 + z/2
def test_branch_bug(): assert hyperexpand(hyper((-S(1)/3, S(1)/2), (S(2)/3, S(3)/2), -z)) == \ -z**S('1/3')*lowergamma(exp_polar(I*pi)/3, z)/5 \ + sqrt(pi)*erf(sqrt(z))/(5*sqrt(z)) assert hyperexpand(meijerg([S(7)/6, 1], [], [S(2)/3], [S(1)/6, 0], z)) == \ 2*z**S('2/3')*(2*sqrt(pi)*erf(sqrt(z))/sqrt(z) - 2*lowergamma( S(2)/3, z)/z**S('2/3'))*gamma(S(2)/3)/gamma(S(5)/3)
def test_meijerg_expand(): from sympy import combsimp, simplify # from mpmath docs assert hyperexpand(meijerg([[], []], [[0], []], -z)) == exp(z) assert hyperexpand(meijerg([[1, 1], []], [[1], [0]], z)) == \ log(z + 1) assert hyperexpand(meijerg([[1, 1], []], [[1], [1]], z)) == \ z/(z + 1) assert hyperexpand(meijerg([[], []], [[S(1)/2], [0]], (z/2)**2)) \ == sin(z)/sqrt(pi) assert hyperexpand(meijerg([[], []], [[0], [S(1)/2]], (z/2)**2)) \ == cos(z)/sqrt(pi) assert can_do_meijer([], [a], [a - 1, a - S.Half], []) assert can_do_meijer([], [], [a/2], [-a/2], False) # branches... assert can_do_meijer([a], [b], [a], [b, a - 1]) # wikipedia assert hyperexpand(meijerg([1], [], [], [0], z)) == \ Piecewise((0, abs(z) < 1), (1, abs(1/z) < 1), (meijerg([1], [], [], [0], z), True)) assert hyperexpand(meijerg([], [1], [0], [], z)) == \ Piecewise((1, abs(z) < 1), (0, abs(1/z) < 1), (meijerg([], [1], [0], [], z), True)) # The Special Functions and their Approximations assert can_do_meijer([], [], [a + b/2], [a, a - b/2, a + S.Half]) assert can_do_meijer( [], [], [a], [b], False) # branches only agree for small z assert can_do_meijer([], [S.Half], [a], [-a]) assert can_do_meijer([], [], [a, b], []) assert can_do_meijer([], [], [a, b], []) assert can_do_meijer([], [], [a, a + S.Half], [b, b + S.Half]) assert can_do_meijer([], [], [a, -a], [0, S.Half], False) # dito assert can_do_meijer([], [], [a, a + S.Half, b, b + S.Half], []) assert can_do_meijer([S.Half], [], [0], [a, -a]) assert can_do_meijer([S.Half], [], [a], [0, -a], False) # dito assert can_do_meijer([], [a - S.Half], [a, b], [a - S.Half], False) assert can_do_meijer([], [a + S.Half], [a + b, a - b, a], [], False) assert can_do_meijer([a + S.Half], [], [b, 2*a - b, a], [], False) # This for example is actually zero. assert can_do_meijer([], [], [], [a, b]) # Testing a bug: assert hyperexpand(meijerg([0, 2], [], [], [-1, 1], z)) == \ Piecewise((0, abs(z) < 1), (z*(1 - 1/z**2)/2, abs(1/z) < 1), (meijerg([0, 2], [], [], [-1, 1], z), True)) # Test that the simplest possible answer is returned: assert combsimp( simplify(hyperexpand(meijerg([1], [1 - a], [-a/2, -a/2 + S(1)/2], [], 1/z)))) == \ -2*sqrt(pi)*(sqrt(z + 1) + 1)**a/a # Test that hyper is returned assert hyperexpand(meijerg([1], [], [a], [0, 0], z)) == \ z**a*gamma(a)*hyper( (a,), (a + 1, a + 1), z*exp_polar(I*pi))/gamma(a + 1)**2
def test_hyperexpand_parametric(): assert ( hyperexpand(hyper([a, S(1) / 2 + a], [S(1) / 2], z)) == (1 + sqrt(z)) ** (-2 * a) / 2 + (1 - sqrt(z)) ** (-2 * a) / 2 ) assert hyperexpand(hyper([a, -S(1) / 2 + a], [2 * a], z)) == 2 ** (2 * a - 1) * ((-z + 1) ** (S(1) / 2) + 1) ** ( -2 * a + 1 )
def test_meijerg_lookup(): from sympy import uppergamma assert hyperexpand(meijerg([a], [], [b, a], [], z)) == z ** b * exp(z) * gamma(-a + b + 1) * uppergamma(a - b, z) assert hyperexpand(meijerg([0], [], [0, 0], [], z)) == exp(z) * uppergamma(0, z) assert can_do_meijer([a], [], [b, a + 1], []) assert can_do_meijer([a], [], [b + 2, a], []) assert can_do_meijer([a], [], [b - 2, a], [])
def test_hyperexpand(): # Luke, Y. L. (1969), The Special Functions and Their Approximations, # Volume 1, section 6.2 assert hyperexpand(hyper([], [], z)) == exp(z) assert hyperexpand(hyper([1, 1], [2], -z) * z) == log(1 + z) assert hyperexpand(hyper([], [S.Half], -z ** 2 / 4)) == cos(z) assert hyperexpand(z * hyper([], [S("3/2")], -z ** 2 / 4)) == sin(z) assert hyperexpand(hyper([S("1/2"), S("1/2")], [S("3/2")], z ** 2) * z) == asin(z)
def test_hyperexpand_special(): assert hyperexpand(hyper([a, b], [c], 1)) == \ gamma(c)*gamma(c - a - b)/gamma(c - a)/gamma(c - b) assert hyperexpand(hyper([a, b], [1 + a - b], -1)) == \ gamma(1 + a/2)*gamma(1 + a - b)/gamma(1 + a)/gamma(1 + a/2 - b) assert hyperexpand(hyper([a, b], [1 + b - a], -1)) == \ gamma(1 + b/2)*gamma(1 + b - a)/gamma(1 + b)/gamma(1 + b/2 - a) assert hyperexpand(meijerg([1 - z - a/2], [1 - z + a/2], [b/2], [-b/2], 1)) == \ gamma(1 - 2*z)*gamma(z + a/2 + b/2)/gamma(1 - z + a/2 - b/2) \ /gamma(1 - z - a/2 + b/2)/gamma(1 - z + a/2 + b/2)
def test_hyperexpand(): # Luke, Y. L. (1969), The Special Functions and Their Approximations, # Volume 1, section 6.2 assert hyperexpand(hyper([], [], z)) == exp(z) assert hyperexpand(hyper([1, 1], [2], -z)*z) == log(1 + z) assert hyperexpand(hyper([], [S.Half], -z**2/4)) == cos(z) assert hyperexpand(z*hyper([], [S('3/2')], -z**2/4)) == sin(z) assert hyperexpand(hyper([S('1/2'), S('1/2')], [S('3/2')], z**2)*z) \ == asin(z) assert isinstance(Sum(binomial(2, z)*z**2, (z, 0, a)).doit(), Expr)
def test_hyperexpand_bases(): assert ( hyperexpand(hyper([2], [a], z)) == a + z ** (-a + 1) * (-a ** 2 + 3 * a + z * (a - 1) - 2) * exp(z) * lowergamma(a - 1, z) - 1 ) # TODO [a+1, a-S.Half], [2*a] assert hyperexpand(hyper([1, 2], [3], z)) == -2 / z - 2 * log(exp_polar(-I * pi) * z + 1) / z ** 2 assert hyperexpand(hyper([S.Half, 2], [S(3) / 2], z)) == -1 / (2 * z - 2) + log((sqrt(z) + 1) / (-sqrt(z) + 1)) / ( 4 * sqrt(z) ) assert hyperexpand(hyper([S(1) / 2, S(1) / 2], [S(5) / 2], z)) == (-3 * z + 3) / 4 / (z * sqrt(-z + 1)) + ( 6 * z - 3 ) * asin(sqrt(z)) / (4 * z ** (S(3) / 2)) assert hyperexpand(hyper([1, 2], [S(3) / 2], z)) == -1 / (2 * z - 2) - asin(sqrt(z)) / ( sqrt(z) * (2 * z - 2) * sqrt(-z + 1) ) assert hyperexpand(hyper([-S.Half - 1, 1, 2], [S.Half, 3], z)) == sqrt(z) * (6 * z / 7 - S(6) / 5) * atanh( sqrt(z) ) + (-30 * z ** 2 + 32 * z - 6) / 35 / z - 6 * log(-z + 1) / (35 * z ** 2) assert hyperexpand(hyper([1 + S.Half, 1, 1], [2, 2], z)) == -4 * log(sqrt(-z + 1) / 2 + S(1) / 2) / z # TODO hyperexpand(hyper([a], [2*a + 1], z)) # TODO [S.Half, a], [S(3)/2, a+1] assert hyperexpand(hyper([2], [b, 1], z)) == z ** (-b / 2 + S(1) / 2) * besseli(b - 1, 2 * sqrt(z)) * gamma( b ) + z ** (-b / 2 + 1) * besseli(b, 2 * sqrt(z)) * gamma(b)
def test_shifted_sum(): from sympy import simplify assert ( simplify(hyperexpand(z ** 4 * hyper([2], [3, S("3/2")], -z ** 2))) == z * sin(2 * z) + (-z ** 2 + S.Half) * cos(2 * z) - S.Half )
def test_meijerg_with_Floats(): # see issue #10681 from sympy import RR f = meijerg(((3.0, 1), ()), ((S(3)/2,), (0,)), z) a = -2.3632718012073 g = a*z**(S(3)/2)*hyper((-0.5, S(3)/2), (S(5)/2,), z*exp_polar(I*pi)) assert RR.almosteq((hyperexpand(f)/g).n(), 1.0, 1e-12)
def can_do_meijer(a1, a2, b1, b2, numeric=True): """ This helper function tries to hyperexpand() the meijer g-function corresponding to the parameters a1, a2, b1, b2. It returns False if this expansion still contains g-functions. If numeric is True, it also tests the so-obtained formula numerically (at random values) and returns False if the test fails. Else it returns True. """ from sympy import unpolarify, expand r = hyperexpand(meijerg(a1, a2, b1, b2, z)) if r.has(meijerg): return False # NOTE hyperexpand() returns a truly branched function, whereas numerical # evaluation only works on the main branch. Since we are evaluating on # the main branch, this should not be a problem, but expressions like # exp_polar(I*pi/2*x)**a are evaluated incorrectly. We thus have to get # rid of them. The expand heuristically does this... r = unpolarify(expand(r, force=True, power_base=True, power_exp=False, mul=False, log=False, multinomial=False, basic=False)) if not numeric: return True repl = {} for n, a in enumerate(meijerg(a1, a2, b1, b2, z).free_symbols - set([z])): repl[a] = randcplx(n) return tn(meijerg(a1, a2, b1, b2, z).subs(repl), r.subs(repl), z)
def test_meijerg_lookup(): from sympy import uppergamma, Si, Ci assert hyperexpand(meijerg([a], [], [b, a], [], z)) == \ z**b*exp(z)*gamma(-a + b + 1)*uppergamma(a - b, z) assert hyperexpand(meijerg([0], [], [0, 0], [], z)) == \ exp(z)*uppergamma(0, z) assert can_do_meijer([a], [], [b, a + 1], []) assert can_do_meijer([a], [], [b + 2, a], []) assert can_do_meijer([a], [], [b - 2, a], []) assert hyperexpand(meijerg([a], [], [a, a, a - S(1)/2], [], z)) == \ -sqrt(pi)*z**(a - S(1)/2)*(2*cos(2*sqrt(z))*(Si(2*sqrt(z)) - pi/2) - 2*sin(2*sqrt(z))*Ci(2*sqrt(z))) == \ hyperexpand(meijerg([a], [], [a, a - S(1)/2, a], [], z)) == \ hyperexpand(meijerg([a], [], [a - S(1)/2, a, a], [], z)) assert can_do_meijer([a - 1], [], [a + 2, a - S(3)/2, a + 1], [])
def can_do(ap, bq, numerical=True): r = hyperexpand(hyper(ap, bq, z)) if r.has(hyper): return False if not numerical: return True repl = {} for n, a in enumerate(r.free_symbols - set([z])): repl[a] = randcplx(n) return tn(hyper(ap, bq, z).subs(repl), r.subs(repl), z)
def test_meijerg_expand(): # from mpmath docs assert hyperexpand(meijerg([[],[]], [[0],[]], -z)) == exp(z) assert hyperexpand(meijerg([[1,1],[]], [[1],[0]], z)) == \ log(z + 1) assert hyperexpand(meijerg([[1,1],[]], [[1],[1]], z)) == \ z/(z + 1) assert hyperexpand(meijerg([[],[]], [[S(1)/2],[0]], (z/2)**2)) \ == sin(z)/sqrt(pi) assert hyperexpand(meijerg([[],[]], [[0], [S(1)/2]], (z/2)**2)) \ == cos(z)/sqrt(pi) assert can_do_meijer([], [a], [a-1, a-S.Half], []) assert can_do_meijer([], [], [a/2], [-a/2], False) # branches... assert can_do_meijer([a], [b], [a], [b, a - 1]) # wikipedia assert hyperexpand(meijerg([1], [], [], [0], z)) == \ Piecewise((0, abs(z) < 1), (1, abs(1/z) < 1), (meijerg([1], [], [], [0], z), True)) assert hyperexpand(meijerg([], [1], [0], [], z)) == \ Piecewise((1, abs(z) < 1), (0, abs(1/z) < 1), (meijerg([], [1], [0], [], z), True)) # The Special Functions and their Approximations assert can_do_meijer([], [], [a + b/2], [a, a - b/2, a + S.Half]) assert can_do_meijer([], [], [a], [b], False) # branches only agree for small z assert can_do_meijer([], [S.Half], [a], [-a]) assert can_do_meijer([], [], [a, b], []) assert can_do_meijer([], [], [a, b], []) assert can_do_meijer([], [], [a, a+S.Half], [b, b+S.Half]) assert can_do_meijer([], [], [a, -a], [0, S.Half], False) # dito assert can_do_meijer([], [], [a, a+S.Half, b, b+S.Half], []) assert can_do_meijer([S.Half], [], [0], [a, -a]) assert can_do_meijer([S.Half], [], [a], [0, -a], False) # dito assert can_do_meijer([], [a - S.Half], [a, b], [a - S.Half], False) assert can_do_meijer([], [a+S.Half], [a+b, a-b, a], [], False) assert can_do_meijer([a+S.Half], [], [b, 2*a-b, a], [], False) # This for example is actually zero. assert can_do_meijer([], [], [], [a, b]) # Testing a bug: assert hyperexpand(meijerg([0, 2], [], [], [-1, 1], z)) == \ Piecewise((0, abs(z) < 1), (z*(1 - 1/z**2)/2, abs(1/z) < 1), (meijerg([0, 2], [], [], [-1, 1], z), True))
def can_do(ap, bq, numerical=True, div=1, lowerplane=False): from sympy import exp_polar, exp r = hyperexpand(hyper(ap, bq, z)) if r.has(hyper): return False if not numerical: return True repl = {} for n, a in enumerate(r.free_symbols - set([z])): repl[a] = randcplx(n) / div [a, b, c, d] = [2, -1, 3, 1] if lowerplane: [a, b, c, d] = [2, -2, 3, -1] return tn(hyper(ap, bq, z).subs(repl), r.replace(exp_polar, exp).subs(repl), z, a=a, b=b, c=c, d=d)
def t(m, a, b): from sympy import sympify, Piecewise a, b = sympify([a, b]) m_ = m m = hyperexpand(m) if not m == Piecewise((a, abs(z) < 1), (b, abs(1/z) < 1), (m_, True)): return False if not (m.args[0].args[0] == a and m.args[1].args[0] == b): return False z0 = randcplx()/10 if abs(m.subs(z, z0).n() - a.subs(z, z0).n()).n() > 1e-10: return False if abs(m.subs(z, 1/z0).n() - b.subs(z, 1/z0).n()).n() > 1e-10: return False return True
def can_do_meijer(a1, a2, b1, b2, numeric=True): """ This helper function tries to hyperexpand() the meijer g-function corresponding to the parameters a1, a2, b1, b2. It returns False if this expansion still contains g-functions. If numeric is True, it also tests the so-obtained formula numerically (at random values) and returns False if the test fails. Else it returns True. """ r = hyperexpand(meijerg(a1, a2, b1, b2, z)) if r.has(meijerg): return False if not numeric: return True repl = {} for n, a in enumerate(meijerg(a1, a2, b1, b2, z).free_symbols - set([z])): repl[a] = randcplx(n) return tn(meijerg(a1, a2, b1, b2, z).subs(repl), r.subs(repl), z)
def test_hyperexpand_bases(): assert hyperexpand(hyper([2], [a], z)) == \ a + z**(-a + 1)*(-a**2 + 3*a + z*(a - 1) - 2)*exp(z)*lowergamma(a - 1, z) - 1 # TODO [a+1, a-S.Half], [2*a] assert hyperexpand(hyper([1, 2], [3], z)) == -2/z - 2*log(-z + 1)/z**2 assert hyperexpand(hyper([S.Half, 2], [S(3)/2], z)) == \ -1/(2*z - 2) + log((z**(S(1)/2) + 1)/(-z**(S(1)/2) + 1))/(4*z**(S(1)/2)) assert hyperexpand(hyper([S(1)/2, S(1)/2], [S(5)/2], z)) == \ (-3*z + 3)/(4*z*(-z + 1)**(S(1)/2)) \ + (6*z - 3)*asin(z**(S(1)/2))/(4*z**(S(3)/2)) assert hyperexpand(hyper([1, 2], [S(3)/2], z)) == -1/(2*z - 2) \ - asin(z**(S(1)/2))/(z**(S(1)/2)*(2*z - 2)*(-z + 1)**(S(1)/2)) assert hyperexpand(hyper([-S.Half - 1, 1, 2], [S.Half, 3], z)) == \ z**(S(1)/2)*(6*z/7 - S(6)/5)*atanh(z**(S(1)/2)) \ + (-30*z**2 + 32*z - 6)/(35*z) - 6*log(-z + 1)/(35*z**2) assert hyperexpand(hyper([1+S.Half, 1, 1], [2, 2], z)) == \ -4*log((-z + 1)**(S(1)/2)/2 + S(1)/2)/z # TODO hyperexpand(hyper([a], [2*a + 1], z)) # TODO [S.Half, a], [S(3)/2, a+1] assert hyperexpand(hyper([2], [b, 1], z)) == \ z**(-b/2 + S(1)/2)*besseli(b - 1, 2*z**(S(1)/2))*gamma(b) \ + z**(-b/2 + 1)*besseli(b, 2*z**(S(1)/2))*gamma(b)
def can_do(ap, bq, numerical=True, div=1, lowerplane=False): from sympy import exp_polar, exp r = hyperexpand(hyper(ap, bq, z)) if r.has(hyper): return False if not numerical: return True repl = {} randsyms = r.free_symbols - {z} while randsyms: # Only randomly generated parameters are checked. for n, a in enumerate(randsyms): repl[a] = randcplx(n)/div if not any([b.is_Integer and b <= 0 for b in Tuple(*bq).subs(repl)]): break [a, b, c, d] = [2, -1, 3, 1] if lowerplane: [a, b, c, d] = [2, -2, 3, -1] return tn( hyper(ap, bq, z).subs(repl), r.replace(exp_polar, exp).subs(repl), z, a=a, b=b, c=c, d=d)
def test_bug(): h = hyper([-1, 1], [z], -1) assert hyperexpand(h) == (z + 1) / z
def test_omgissue_203(): h = hyper((-5, -3, -4), (-6, -6), 1) assert hyperexpand(h) == Rational(1, 30) h = hyper((-6, -7, -5), (-6, -6), 1) assert hyperexpand(h) == -Rational(1, 6)
def test_bug(): h = hyper([-1, 1], [z], -1) assert hyperexpand(h) == (z + 1)/z
def from_meijerg(func, x0=0, evalf=False): """ Converts a Meijer G-function to Holonomic. func is the Hypergeometric Function and x0 be the point at which initial conditions are required. Examples ======= >>> from sympy.holonomic.holonomic import from_meijerg, DifferentialOperators >>> from sympy import symbols, meijerg, S >>> x = symbols('x') >>> from_meijerg(meijerg(([], []), ([S(1)/2], [0]), x**2/4)) HolonomicFunction((1) + (1)Dx**2, x), f(0) = 0, f'(0) = 1/sqrt(pi) """ a = func.ap b = func.bq n = len(func.an) m = len(func.bm) p = len(a) z = func.args[2] x = z.atoms(Symbol).pop() R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') # compute the differential equation satisfied by the # Meijer G-function. mnp = (-1)**(m + n - p) r1 = x * mnp for i in range(len(a)): r1 *= x * Dx + 1 - a[i] r2 = 1 for i in range(len(b)): r2 *= x * Dx - b[i] sol = r1 - r2 simp = hyperexpand(func) if isinstance(simp, Infinity) or isinstance(simp, NegativeInfinity): return HolonomicFunction(sol, x).composition(z) def _find_conditions(simp, x, x0, order, evalf=False): y0 = [] for i in range(order): if evalf: val = simp.subs(x, x0).evalf() else: val = simp.subs(x, x0) if (val.is_finite is not None and not val.is_finite) or isinstance(val, NaN): return None y0.append(val) simp = simp.diff() return y0 # computing initial conditions if not isinstance(simp, meijerg): y0 = _find_conditions(simp, x, x0, sol.order) while not y0: x0 += 1 y0 = _find_conditions(simp, x, x0, sol.order) return HolonomicFunction(sol, x).composition(z, x0, y0) if isinstance(simp, meijerg): x0 = 1 y0 = _find_conditions(simp, x, x0, sol.order, evalf) while not y0: x0 += 1 y0 = _find_conditions(simp, x, x0, sol.order, evalf) return HolonomicFunction(sol, x).composition(z, x0, y0) return HolonomicFunction(sol, x).composition(z)
def from_hyper(func, x0=0, evalf=False): """ Converts Hypergeometric Function to Holonomic. func is the Hypergeometric Function and x0 be the point at which initial conditions are required. Examples ======= >>> from sympy.holonomic.holonomic import from_hyper, DifferentialOperators >>> from sympy import symbols, hyper, S >>> x = symbols('x') >>> from_hyper(hyper([], [S(3)/2], x**2/4)) HolonomicFunction((-x) + (2)Dx + (x)Dx**2, x), f(1) = sinh(1), f'(1) = -sinh(1) + cosh(1) """ a = func.ap b = func.bq z = func.args[2] x = z.atoms(Symbol).pop() R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') # generalized hypergeometric differential equation r1 = 1 for i in range(len(a)): r1 = r1 * (x * Dx + a[i]) r2 = Dx for i in range(len(b)): r2 = r2 * (x * Dx + b[i] - 1) sol = r1 - r2 simp = hyperexpand(func) if isinstance(simp, Infinity) or isinstance(simp, NegativeInfinity): return HolonomicFunction(sol, x).composition(z) def _find_conditions(simp, x, x0, order, evalf=False): y0 = [] for i in range(order): if evalf: val = simp.subs(x, x0).evalf() else: val = simp.subs(x, x0) # return None if it is Infinite or NaN if (val.is_finite is not None and not val.is_finite) or isinstance(val, NaN): return None y0.append(val) simp = simp.diff() return y0 # if the function is known symbolically if not isinstance(simp, hyper): y0 = _find_conditions(simp, x, x0, sol.order) while not y0: # if values don't exist at 0, then try to find initial # conditions at 1. If it doesn't exist at 1 too then # try 2 and so on. x0 += 1 y0 = _find_conditions(simp, x, x0, sol.order) return HolonomicFunction(sol, x).composition(z, x0, y0) if isinstance(simp, hyper): x0 = 1 # use evalf if the function can't be simpified y0 = _find_conditions(simp, x, x0, sol.order, evalf) while not y0: x0 += 1 y0 = _find_conditions(simp, x, x0, sol.order, evalf) return HolonomicFunction(sol, x).composition(z, x0, y0) return HolonomicFunction(sol, x).composition(z)
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 SymPy. 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 sympy import simplify, cos, sin >>> from sympy.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 sympy 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:`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 sympy 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 sympy 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 sympy 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 sympy.simplify.hyperexpand import hyperexpand from sympy.functions.special.bessel import BesselBase from sympy import Sum, Product if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack return expr if not isinstance(expr, (Add, Mul, Pow, ExpBase)): 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, ExpBase): 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 u(an, ap, bm, bq): m = meijerg(an, ap, bm, bq, z) m2 = hyperexpand(m, allow_hyper=True) if m2.has(meijerg) and not (m2.is_Piecewise and len(m2.args) == 3): return False return tn(m, m2, z)
def from_hyper(func, x0=0, evalf=False): """ Converts Hypergeometric Function to Holonomic. func is the Hypergeometric Function and x0 be the point at which initial conditions are required. Examples ======= >>> from sympy.holonomic.holonomic import from_hyper, DifferentialOperators >>> from sympy import symbols, hyper, S >>> x = symbols('x') >>> from_hyper(hyper([], [S(3)/2], x**2/4)) HolonomicFunction((-x) + (2)Dx + (x)Dx**2, x), f(1) = sinh(1) , f'(1) = -sinh(1) + cosh(1) """ a = func.ap b = func.bq z = func.args[2] x = z.atoms(Symbol).pop() R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') r1 = 1 for i in range(len(a)): r1 = r1 * (x * Dx + a[i]) r2 = Dx for i in range(len(b)): r2 = r2 * (x * Dx + b[i] - 1) sol = r1 - r2 simp = hyperexpand(func) if isinstance(simp, Infinity) or isinstance(simp, NegativeInfinity): return HolonomicFunction(sol, x).composition(z) def _find_conditions(simp, x, x0, order, evalf=False): y0 = [] for i in range(order): if evalf: val = simp.subs(x, x0).evalf() else: val = simp.subs(x, x0) if isinstance(val, Infinity) or isinstance(val, NaN): return None y0.append(val) simp = simp.diff() return y0 if not isinstance(simp, hyper): y0 = _find_conditions(simp, x, x0, sol.order) while not y0: x0 += 1 y0 = _find_conditions(simp, x, x0, sol.order) return HolonomicFunction(sol, x, x0, y0).composition(z) if isinstance(simp, hyper): x0 = 1 y0 = _find_conditions(simp, x, x0, sol.order, evalf) while not y0: x0 += 1 y0 = _find_conditions(simp, x, x0, sol.order, evalf) return HolonomicFunction(sol, x, x0, y0).composition(z) return HolonomicFunction(sol, x).composition(z)
def test_hyperexpand_parametric(): assert hyperexpand(hyper([a, S(1)/2 + a], [S(1)/2], z)) \ == (1 + sqrt(z))**(-2*a)/2 + (1 - sqrt(z))**(-2*a)/2 assert hyperexpand(hyper([a, -S(1)/2 + a], [2*a], z)) \ == 2**(2*a - 1)*((-z + 1)**(S(1)/2) + 1)**(-2*a + 1)
def test_shifted_sum(): from sympy import simplify assert simplify(hyperexpand(z**4*hyper([2], [3, S('3/2')], -z**2))) \ == z*sin(2*z) + (-z**2 + S.Half)*cos(2*z) - S.Half
def test_meijerg_expand(): from sympy import gammasimp, simplify # from mpmath docs assert hyperexpand(meijerg([[], []], [[0], []], -z)) == exp(z) assert hyperexpand(meijerg([[1, 1], []], [[1], [0]], z)) == \ log(z + 1) assert hyperexpand(meijerg([[1, 1], []], [[1], [1]], z)) == \ z/(z + 1) assert hyperexpand(meijerg([[], []], [[S(1)/2], [0]], (z/2)**2)) \ == sin(z)/sqrt(pi) assert hyperexpand(meijerg([[], []], [[0], [S(1)/2]], (z/2)**2)) \ == cos(z)/sqrt(pi) assert can_do_meijer([], [a], [a - 1, a - S.Half], []) assert can_do_meijer([], [], [a / 2], [-a / 2], False) # branches... assert can_do_meijer([a], [b], [a], [b, a - 1]) # wikipedia assert hyperexpand(meijerg([1], [], [], [0], z)) == \ Piecewise((0, abs(z) < 1), (1, abs(1/z) < 1), (meijerg([1], [], [], [0], z), True)) assert hyperexpand(meijerg([], [1], [0], [], z)) == \ Piecewise((1, abs(z) < 1), (0, abs(1/z) < 1), (meijerg([], [1], [0], [], z), True)) # The Special Functions and their Approximations assert can_do_meijer([], [], [a + b / 2], [a, a - b / 2, a + S.Half]) assert can_do_meijer([], [], [a], [b], False) # branches only agree for small z assert can_do_meijer([], [S.Half], [a], [-a]) assert can_do_meijer([], [], [a, b], []) assert can_do_meijer([], [], [a, b], []) assert can_do_meijer([], [], [a, a + S.Half], [b, b + S.Half]) assert can_do_meijer([], [], [a, -a], [0, S.Half], False) # dito assert can_do_meijer([], [], [a, a + S.Half, b, b + S.Half], []) assert can_do_meijer([S.Half], [], [0], [a, -a]) assert can_do_meijer([S.Half], [], [a], [0, -a], False) # dito assert can_do_meijer([], [a - S.Half], [a, b], [a - S.Half], False) assert can_do_meijer([], [a + S.Half], [a + b, a - b, a], [], False) assert can_do_meijer([a + S.Half], [], [b, 2 * a - b, a], [], False) # This for example is actually zero. assert can_do_meijer([], [], [], [a, b]) # Testing a bug: assert hyperexpand(meijerg([0, 2], [], [], [-1, 1], z)) == \ Piecewise((0, abs(z) < 1), (z/2 - 1/(2*z), abs(1/z) < 1), (meijerg([0, 2], [], [], [-1, 1], z), True)) # Test that the simplest possible answer is returned: assert gammasimp(simplify(hyperexpand( meijerg([1], [1 - a], [-a/2, -a/2 + S(1)/2], [], 1/z)))) == \ -2*sqrt(pi)*(sqrt(z + 1) + 1)**a/a # Test that hyper is returned assert hyperexpand(meijerg([1], [], [a], [0, 0], z)) == hyper( (a, ), (a + 1, a + 1), z * exp_polar(I * pi)) * z**a * gamma(a) / gamma(a + 1)**2 # Test place option f = meijerg(((0, 1), ()), ((S(1) / 2, ), (0, )), z**2) assert hyperexpand(f) == sqrt(pi) / sqrt(1 + z**(-2)) assert hyperexpand(f, place=0) == sqrt(pi) * z / sqrt(z**2 + 1)
def test_Mod1_behavior(): from sympy import Symbol, simplify, lowergamma n = Symbol('n', integer=True) # Note: this should not hang. assert simplify(hyperexpand(meijerg([1], [], [n + 1], [0], z))) == \ lowergamma(n + 1, z)
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 SymPy. 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 sympy import simplify, cos, sin >>> from sympy.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 sympy 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:`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 sympy 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 sympy 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 sympy 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 sympy.simplify.hyperexpand import hyperexpand from sympy.functions.special.bessel import BesselBase from sympy import Sum, Product if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack return expr if not isinstance(expr, (Add, Mul, Pow, ExpBase)): if isinstance(expr, Function) and hasattr(expr, "inverse"): if len(expr.args) == 1 and len(expr.args[0].args) == 1 and \ isinstance(expr.args[0], expr.inverse(argindex=1)): return simplify(expr.args[0].args[0], ratio=ratio, measure=measure, fu=fu) 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, ExpBase): 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 test_lerchphi(): from sympy import gammasimp, exp_polar, polylog, log, lerchphi assert hyperexpand(hyper([1, a], [a + 1], z) / a) == lerchphi(z, 1, a) assert hyperexpand(hyper([1, a, a], [a + 1, a + 1], z) / a**2) == lerchphi( z, 2, a) assert hyperexpand(hyper([1, a, a, a], [a + 1, a + 1, a + 1], z)/a**3) == \ lerchphi(z, 3, a) assert hyperexpand(hyper([1] + [a]*10, [a + 1]*10, z)/a**10) == \ lerchphi(z, 10, a) assert gammasimp( hyperexpand(meijerg([0, 1 - a], [], [0], [-a], exp_polar(-I * pi) * z))) == lerchphi(z, 1, a) assert gammasimp( hyperexpand( meijerg([0, 1 - a, 1 - a], [], [0], [-a, -a], exp_polar(-I * pi) * z))) == lerchphi(z, 2, a) assert gammasimp( hyperexpand( meijerg([0, 1 - a, 1 - a, 1 - a], [], [0], [-a, -a, -a], exp_polar(-I * pi) * z))) == lerchphi(z, 3, a) assert hyperexpand(z * hyper([1, 1], [2], z)) == -log(1 + -z) assert hyperexpand(z * hyper([1, 1, 1], [2, 2], z)) == polylog(2, z) assert hyperexpand(z * hyper([1, 1, 1, 1], [2, 2, 2], z)) == polylog(3, z) assert hyperexpand(hyper([1, a, 1 + S(1)/2], [a + 1, S(1)/2], z)) == \ -2*a/(z - 1) + (-2*a**2 + a)*lerchphi(z, 1, a) # Now numerical tests. These make sure reductions etc are carried out # correctly # a rational function (polylog at negative integer order) assert can_do([2, 2, 2], [1, 1]) # NOTE these contain log(1-x) etc ... better make sure we have |z| < 1 # reduction of order for polylog assert can_do([1, 1, 1, b + 5], [2, 2, b], div=10) # reduction of order for lerchphi # XXX lerchphi in mpmath is flaky assert can_do([1, a, a, a, b + 5], [a + 1, a + 1, a + 1, b], numerical=False) # test a bug from sympy import Abs assert hyperexpand(hyper([S(1)/2, S(1)/2, S(1)/2, 1], [S(3)/2, S(3)/2, S(3)/2], S(1)/4)) == \ Abs(-polylog(3, exp_polar(I*pi)/2) + polylog(3, S(1)/2))
def test_shifted_sum(): from sympy import simplify assert simplify(hyperexpand(z**4*hyper([2], [3, S('3/2')], -z**2))) \ == -S(1)/2 + cos(2*z)/2 + z*sin(2*z) - z**2*cos(2*z)
def test_lerchphi(): from sympy import combsimp, exp_polar, polylog, log, lerchphi assert hyperexpand(hyper([1, a], [a + 1], z)/a) == lerchphi(z, 1, a) assert hyperexpand( hyper([1, a, a], [a + 1, a + 1], z)/a**2) == lerchphi(z, 2, a) assert hyperexpand(hyper([1, a, a, a], [a + 1, a + 1, a + 1], z)/a**3) == \ lerchphi(z, 3, a) assert hyperexpand(hyper([1] + [a]*10, [a + 1]*10, z)/a**10) == \ lerchphi(z, 10, a) assert combsimp(hyperexpand(meijerg([0, 1 - a], [], [0], [-a], exp_polar(-I*pi)*z))) == lerchphi(z, 1, a) assert combsimp(hyperexpand(meijerg([0, 1 - a, 1 - a], [], [0], [-a, -a], exp_polar(-I*pi)*z))) == lerchphi(z, 2, a) assert combsimp(hyperexpand(meijerg([0, 1 - a, 1 - a, 1 - a], [], [0], [-a, -a, -a], exp_polar(-I*pi)*z))) == lerchphi(z, 3, a) assert hyperexpand(z*hyper([1, 1], [2], z)) == -log(1 + -z) assert hyperexpand(z*hyper([1, 1, 1], [2, 2], z)) == polylog(2, z) assert hyperexpand(z*hyper([1, 1, 1, 1], [2, 2, 2], z)) == polylog(3, z) assert hyperexpand(hyper([1, a, 1 + S(1)/2], [a + 1, S(1)/2], z)) == \ -2*a/(z - 1) + (-2*a**2 + a)*lerchphi(z, 1, a) # Now numerical tests. These make sure reductions etc are carried out # correctly # a rational function (polylog at negative integer order) assert can_do([2, 2, 2], [1, 1]) # NOTE these contain log(1-x) etc ... better make sure we have |z| < 1 # reduction of order for polylog assert can_do([1, 1, 1, b + 5], [2, 2, b], div=10) # reduction of order for lerchphi # XXX lerchphi in mpmath is flaky assert can_do( [1, a, a, a, b + 5], [a + 1, a + 1, a + 1, b], numerical=False) # test a bug from sympy import Abs assert hyperexpand(hyper([S(1)/2, S(1)/2, S(1)/2, 1], [S(3)/2, S(3)/2, S(3)/2], S(1)/4)) == \ Abs(-polylog(3, exp_polar(I*pi)/2) + polylog(3, S(1)/2))
def test_to_meijerg(): x = symbols('x') assert hyperexpand(expr_to_holonomic(sin(x)).to_meijerg()) == sin(x) assert hyperexpand(expr_to_holonomic(cos(x)).to_meijerg()) == cos(x) assert hyperexpand(expr_to_holonomic(exp(x)).to_meijerg()) == exp(x) assert hyperexpand(expr_to_holonomic( log(x)).to_meijerg()).simplify() == log(x) assert expr_to_holonomic(4 * x**2 / 3 + 7).to_meijerg() == 4 * x**2 / 3 + 7 assert hyperexpand( expr_to_holonomic(besselj(2, x), lenics=3).to_meijerg()) == besselj(2, x) p = hyper((Rational(-1, 2), -3), (), x) assert from_hyper(p).to_meijerg() == hyperexpand(p) p = hyper((S.One, S(3)), (S(2), ), x) assert (hyperexpand(from_hyper(p).to_meijerg()) - hyperexpand(p)).expand() == 0 p = from_hyper(hyper((-2, -3), (S.Half, ), x)) s = hyperexpand(hyper((-2, -3), (S.Half, ), x)) C_0 = Symbol('C_0') C_1 = Symbol('C_1') D_0 = Symbol('D_0') assert (hyperexpand(p.to_meijerg()).subs({ C_0: 1, D_0: 0 }) - s).simplify() == 0 p.y0 = {0: [1], S.Half: [0]} assert (hyperexpand(p.to_meijerg()) - s).simplify() == 0 p = expr_to_holonomic(besselj(S.Half, x), initcond=False) assert ( p.to_expr() - (D_0 * sin(x) + C_0 * cos(x) + C_1 * sin(x)) / sqrt(x)).simplify() == 0 p = expr_to_holonomic( besselj(S.Half, x), y0={Rational(-1, 2): [sqrt(2) / sqrt(pi), sqrt(2) / sqrt(pi)]}) assert (p.to_expr() - besselj(S.Half, x) - besselj(Rational(-1, 2), x)).simplify() == 0