|
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 |
|
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): |
|
|
|
|
|
x = func.args[0] |
|
|
|
|
|
I1 = factor(cancel(A.diff(x)/2 + A**2/4 - B)) |
|
|
|
|
|
J1 = factor(cancel(x**2*I1 + S(1)/4)) |
|
num, dem = J1.as_numer_denom() |
|
num = powdenest(expand(num)) |
|
dem = powdenest(expand(dem)) |
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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))) |
|
|
|
|
|
|
|
|
|
|
|
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 = [] |
|
|
|
for arg in dem_args: |
|
if arg.has(x): |
|
if isinstance(arg, Pow): |
|
|
|
dem_pow.append(arg.as_base_exp()[1]) |
|
sing_point.append(list(roots(arg.as_base_exp()[0], x).keys())[0]) |
|
else: |
|
|
|
dem_pow.append(arg.as_base_exp()[1]) |
|
sing_point.append(list(roots(arg, x).keys())[0]) |
|
|
|
dem_pow.sort() |
|
|
|
|
|
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 = ((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]: |
|
|
|
eqs = [] |
|
sing_eqs = [-beta/alpha, -delta/gamma, (delta-beta)/(alpha-gamma)] |
|
|
|
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)) |
|
|
|
_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 |
|
|
|
|
|
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() |
|
|
|
|
|
dict_I0 = {x**2:s**2 - 1, x:(2*(1-r)*c + (r+s)*(r-s)), 1:c*(c-2)} |
|
|
|
dict_I.update(collect(expand(cancel(I*I0_dem)), [x**2, x], evaluate=False)) |
|
eqs = [] |
|
|
|
|
|
for key in [x**2, x, 1]: |
|
eqs.append(Eq(dict_I[key], dict_I0[key])) |
|
|
|
|
|
|
|
|
|
|
|
|
|
_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): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
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 |
|
|