|
|
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))) 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<len(sing_point): eqs.append(Eq(sing_eqs[i], sing_point[i])) else: eqs.append(Eq(1/sing_eqs[i], 0)) # solving above equations for the mobius transformation _beta = -alpha*sing_point[0] _delta = -gamma*sing_point[1] _gamma = alpha if len(sing_point) == 3: _gamma = (_beta + sing_point[2]*alpha)/(sing_point[2] - sing_point[1]) mob = (alpha*x + beta)/(gamma*x + delta) mob = mob.subs(beta, _beta) mob = mob.subs(delta, _delta) mob = mob.subs(gamma, _gamma) mob = cancel(mob) t = (beta - delta*x)/(gamma*x - alpha) t = cancel(((t.subs(beta, _beta)).subs(delta, _delta)).subs(gamma, _gamma)) else: mob = x t = x
# applying mobius transformation in I to make it into I0. I = I.subs(x, t) I = I*(t.diff(x))**2 I = factor(I) dict_I = {x**2:0, x:0, 1:0} I0_num, I0_dem = I0.as_numer_denom() # collecting coeff of (x**2, x), of the standerd equation. # substituting (a-b) = s, (a+b) = r dict_I0 = {x**2:s**2 - 1, x:(2*(1-r)*c + (r+s)*(r-s)), 1:c*(c-2)} # collecting coeff of (x**2, x) from I0 of the given equation. dict_I.update(collect(expand(cancel(I*I0_dem)), [x**2, x], evaluate=False)) eqs = [] # We are comparing the coeff of powers of different x, for finding the values of # parameters of standerd equation. for key in [x**2, x, 1]: eqs.append(Eq(dict_I[key], dict_I0[key]))
# We can have many possible roots for the equation. # I am selecting the root on the basis that when we have # standard equation eq = x*(x-1)*f(x).diff(x, 2) + ((a+b+1)*x-c)*f(x).diff(x) + a*b*f(x) # then root should be a, b, c.
_c = 1 - factor(sqrt(1+eqs[2].lhs)) if not _c.has(Symbol): _c = min(list(roots(eqs[2], c))) _s = factor(sqrt(eqs[0].lhs + 1)) _r = _c - factor(sqrt(_c**2 + _s**2 + eqs[1].lhs - 2*_c)) _a = (_r + _s)/2 _b = (_r - _s)/2
rn = {'a':simplify(_a), 'b':simplify(_b), 'c':simplify(_c), 'k':k, 'mobius':mob, 'type':"2F1"} return rn
def equivalence(max_num_pow, dem_pow): # this function is made for checking the equivalence with 2F1 type of equation. # max_num_pow is the value of maximum power of x in numerator # and dem_pow is list of powers of different factor of form (a*x b). # reference from table 1 in paper - "Non-Liouvillian solutions for second order # linear ODEs" by L. Chan, E.S. Cheb-Terrab. # We can extend it for 1F1 and 0F1 type also.
if max_num_pow == 2: if dem_pow in [[2, 2], [2, 2, 2]]: return "2F1" elif max_num_pow == 1: if dem_pow in [[1, 2, 2], [2, 2, 2], [1, 2], [2, 2]]: return "2F1" elif max_num_pow == 0: if dem_pow in [[1, 1, 2], [2, 2], [1, 2, 2], [1, 1], [2], [1, 2], [2, 2]]: return "2F1"
return None
def get_sol_2F1_hypergeometric(eq, func, match_object): x = func.args[0] from sympy.simplify.hyperexpand import hyperexpand from sympy.polys.polytools import factor C0, C1 = get_numbered_constants(eq, num=2) a = match_object['a'] b = match_object['b'] c = match_object['c'] A = match_object['A']
sol = None
if c.is_integer == False: sol = C0*hyper([a, b], [c], x) + C1*hyper([a-c+1, b-c+1], [2-c], x)*x**(1-c) elif c == 1: y2 = Integral(exp(Integral((-(a+b+1)*x + c)/(x**2-x), x))/(hyperexpand(hyper([a, b], [c], x))**2), x)*hyper([a, b], [c], x) sol = C0*hyper([a, b], [c], x) + C1*y2 elif (c-a-b).is_integer == False: sol = C0*hyper([a, b], [1+a+b-c], 1-x) + C1*hyper([c-a, c-b], [1+c-a-b], 1-x)*(1-x)**(c-a-b)
if sol: # applying transformation in the solution subs = match_object['mobius'] dtdx = simplify(1/(subs.diff(x))) _B = ((a + b + 1)*x - c).subs(x, subs)*dtdx _B = factor(_B + ((x**2 -x).subs(x, subs))*(dtdx.diff(x)*dtdx)) _A = factor((x**2 - x).subs(x, subs)*(dtdx**2)) e = exp(logcombine(Integral(cancel(_B/(2*_A)), x), force=True)) sol = sol.subs(x, match_object['mobius']) sol = sol.subs(x, x**match_object['k']) e = e.subs(x, x**match_object['k'])
if not A.is_zero: e1 = Integral(A/2, x) e1 = exp(logcombine(e1, force=True)) sol = cancel((e/e1)*x**((-match_object['k']+1)/2))*sol sol = Eq(func, sol) return sol
sol = cancel((e)*x**((-match_object['k']+1)/2))*sol sol = Eq(func, sol) return sol
|