r''' This module contains the implementation of the 2nd_hypergeometric hint for dsolve. This is an incomplete implementation of the algorithm described in [1]. The algorithm solves 2nd order linear ODEs of the form .. math:: y'' + A(x) y' + B(x) y = 0\text{,} where `A` and `B` are rational functions. The algorithm should find any solution of the form .. math:: y = P(x) _pF_q(..; ..;\frac{\alpha x^k + \beta}{\gamma x^k + \delta})\text{,} where pFq is any of 2F1, 1F1 or 0F1 and `P` is an "arbitrary function". Currently only the 2F1 case is implemented in SymPy but the other cases are described in the paper and could be implemented in future (contributions welcome!). References ========== .. [1] L. Chan, E.S. Cheb-Terrab, Non-Liouvillian solutions for second order linear ODEs, (2004). https://arxiv.org/abs/math-ph/0402063 ''' from sympy.core import S, Pow from sympy.core.function import expand from sympy.core.relational import Eq from sympy.core.symbol import Symbol, Wild from sympy.functions import exp, sqrt, hyper from sympy.integrals import Integral from sympy.polys import roots, gcd from sympy.polys.polytools import cancel, factor from sympy.simplify import collect, simplify, logcombine # type: ignore from sympy.simplify.powsimp import powdenest from sympy.solvers.ode.ode import get_numbered_constants def match_2nd_hypergeometric(eq, func): x = func.args[0] df = func.diff(x) a3 = Wild('a3', exclude=[func, func.diff(x), func.diff(x, 2)]) b3 = Wild('b3', exclude=[func, func.diff(x), func.diff(x, 2)]) c3 = Wild('c3', exclude=[func, func.diff(x), func.diff(x, 2)]) deq = a3*(func.diff(x, 2)) + b3*df + c3*func r = collect(eq, [func.diff(x, 2), func.diff(x), func]).match(deq) if r: if not all(val.is_polynomial() for val in r.values()): n, d = eq.as_numer_denom() eq = expand(n) r = collect(eq, [func.diff(x, 2), func.diff(x), func]).match(deq) if r and r[a3]!=0: A = cancel(r[b3]/r[a3]) B = cancel(r[c3]/r[a3]) return [A, B] else: return [] def equivalence_hypergeometric(A, B, func): # This method for finding the equivalence is only for 2F1 type. # We can extend it for 1F1 and 0F1 type also. x = func.args[0] # making given equation in normal form I1 = factor(cancel(A.diff(x)/2 + A**2/4 - B)) # computing shifted invariant(J1) of the equation J1 = factor(cancel(x**2*I1 + S(1)/4)) num, dem = J1.as_numer_denom() num = powdenest(expand(num)) dem = powdenest(expand(dem)) # this function will compute the different powers of variable(x) in J1. # then it will help in finding value of k. k is power of x such that we can express # J1 = x**k * J0(x**k) then all the powers in J0 become integers. def _power_counting(num): _pow = {0} for val in num: if val.has(x): if isinstance(val, Pow) and val.as_base_exp()[0] == x: _pow.add(val.as_base_exp()[1]) elif val == x: _pow.add(val.as_base_exp()[1]) else: _pow.update(_power_counting(val.args)) return _pow pow_num = _power_counting((num, )) pow_dem = _power_counting((dem, )) pow_dem.update(pow_num) _pow = pow_dem k = gcd(_pow) # computing I0 of the given equation I0 = powdenest(simplify(factor(((J1/k**2) - S(1)/4)/((x**k)**2))), force=True) I0 = factor(cancel(powdenest(I0.subs(x, x**(S(1)/k)), force=True))) # Before this point I0, J1 might be functions of e.g. sqrt(x) but replacing # x with x**(1/k) should result in I0 being a rational function of x or # otherwise the hypergeometric solver cannot be used. Note that k can be a # non-integer rational such as 2/7. if not I0.is_rational_function(x): return None num, dem = I0.as_numer_denom() max_num_pow = max(_power_counting((num, ))) dem_args = dem.args sing_point = [] dem_pow = [] # calculating singular point of I0. for arg in dem_args: if arg.has(x): if isinstance(arg, Pow): # (x-a)**n dem_pow.append(arg.as_base_exp()[1]) sing_point.append(list(roots(arg.as_base_exp()[0], x).keys())[0]) else: # (x-a) type dem_pow.append(arg.as_base_exp()[1]) sing_point.append(list(roots(arg, x).keys())[0]) dem_pow.sort() # checking if equivalence is exists or not. if equivalence(max_num_pow, dem_pow) == "2F1": return {'I0':I0, 'k':k, 'sing_point':sing_point, 'type':"2F1"} else: return None def match_2nd_2F1_hypergeometric(I, k, sing_point, func): x = func.args[0] a = Wild("a") b = Wild("b") c = Wild("c") t = Wild("t") s = Wild("s") r = Wild("r") alpha = Wild("alpha") beta = Wild("beta") gamma = Wild("gamma") delta = Wild("delta") # I0 of the standerd 2F1 equation. I0 = ((a-b+1)*(a-b-1)*x**2 + 2*((1-a-b)*c + 2*a*b)*x + c*(c-2))/(4*x**2*(x-1)**2) if sing_point != [0, 1]: # If singular point is [0, 1] then we have standerd equation. eqs = [] sing_eqs = [-beta/alpha, -delta/gamma, (delta-beta)/(alpha-gamma)] # making equations for the finding the mobius transformation for i in range(3): if i