hypergeometric.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. r'''
  2. This module contains the implementation of the 2nd_hypergeometric hint for
  3. dsolve. This is an incomplete implementation of the algorithm described in [1].
  4. The algorithm solves 2nd order linear ODEs of the form
  5. .. math:: y'' + A(x) y' + B(x) y = 0\text{,}
  6. where `A` and `B` are rational functions. The algorithm should find any
  7. solution of the form
  8. .. math:: y = P(x) _pF_q(..; ..;\frac{\alpha x^k + \beta}{\gamma x^k + \delta})\text{,}
  9. where pFq is any of 2F1, 1F1 or 0F1 and `P` is an "arbitrary function".
  10. Currently only the 2F1 case is implemented in SymPy but the other cases are
  11. described in the paper and could be implemented in future (contributions
  12. welcome!).
  13. References
  14. ==========
  15. .. [1] L. Chan, E.S. Cheb-Terrab, Non-Liouvillian solutions for second order
  16. linear ODEs, (2004).
  17. https://arxiv.org/abs/math-ph/0402063
  18. '''
  19. from sympy.core import S, Pow
  20. from sympy.core.function import expand
  21. from sympy.core.relational import Eq
  22. from sympy.core.symbol import Symbol, Wild
  23. from sympy.functions import exp, sqrt, hyper
  24. from sympy.integrals import Integral
  25. from sympy.polys import roots, gcd
  26. from sympy.polys.polytools import cancel, factor
  27. from sympy.simplify import collect, simplify, logcombine # type: ignore
  28. from sympy.simplify.powsimp import powdenest
  29. from sympy.solvers.ode.ode import get_numbered_constants
  30. def match_2nd_hypergeometric(eq, func):
  31. x = func.args[0]
  32. df = func.diff(x)
  33. a3 = Wild('a3', exclude=[func, func.diff(x), func.diff(x, 2)])
  34. b3 = Wild('b3', exclude=[func, func.diff(x), func.diff(x, 2)])
  35. c3 = Wild('c3', exclude=[func, func.diff(x), func.diff(x, 2)])
  36. deq = a3*(func.diff(x, 2)) + b3*df + c3*func
  37. r = collect(eq,
  38. [func.diff(x, 2), func.diff(x), func]).match(deq)
  39. if r:
  40. if not all(val.is_polynomial() for val in r.values()):
  41. n, d = eq.as_numer_denom()
  42. eq = expand(n)
  43. r = collect(eq, [func.diff(x, 2), func.diff(x), func]).match(deq)
  44. if r and r[a3]!=0:
  45. A = cancel(r[b3]/r[a3])
  46. B = cancel(r[c3]/r[a3])
  47. return [A, B]
  48. else:
  49. return []
  50. def equivalence_hypergeometric(A, B, func):
  51. # This method for finding the equivalence is only for 2F1 type.
  52. # We can extend it for 1F1 and 0F1 type also.
  53. x = func.args[0]
  54. # making given equation in normal form
  55. I1 = factor(cancel(A.diff(x)/2 + A**2/4 - B))
  56. # computing shifted invariant(J1) of the equation
  57. J1 = factor(cancel(x**2*I1 + S(1)/4))
  58. num, dem = J1.as_numer_denom()
  59. num = powdenest(expand(num))
  60. dem = powdenest(expand(dem))
  61. # this function will compute the different powers of variable(x) in J1.
  62. # then it will help in finding value of k. k is power of x such that we can express
  63. # J1 = x**k * J0(x**k) then all the powers in J0 become integers.
  64. def _power_counting(num):
  65. _pow = {0}
  66. for val in num:
  67. if val.has(x):
  68. if isinstance(val, Pow) and val.as_base_exp()[0] == x:
  69. _pow.add(val.as_base_exp()[1])
  70. elif val == x:
  71. _pow.add(val.as_base_exp()[1])
  72. else:
  73. _pow.update(_power_counting(val.args))
  74. return _pow
  75. pow_num = _power_counting((num, ))
  76. pow_dem = _power_counting((dem, ))
  77. pow_dem.update(pow_num)
  78. _pow = pow_dem
  79. k = gcd(_pow)
  80. # computing I0 of the given equation
  81. I0 = powdenest(simplify(factor(((J1/k**2) - S(1)/4)/((x**k)**2))), force=True)
  82. I0 = factor(cancel(powdenest(I0.subs(x, x**(S(1)/k)), force=True)))
  83. num, dem = I0.as_numer_denom()
  84. max_num_pow = max(_power_counting((num, )))
  85. dem_args = dem.args
  86. sing_point = []
  87. dem_pow = []
  88. # calculating singular point of I0.
  89. for arg in dem_args:
  90. if arg.has(x):
  91. if isinstance(arg, Pow):
  92. # (x-a)**n
  93. dem_pow.append(arg.as_base_exp()[1])
  94. sing_point.append(list(roots(arg.as_base_exp()[0], x).keys())[0])
  95. else:
  96. # (x-a) type
  97. dem_pow.append(arg.as_base_exp()[1])
  98. sing_point.append(list(roots(arg, x).keys())[0])
  99. dem_pow.sort()
  100. # checking if equivalence is exists or not.
  101. if equivalence(max_num_pow, dem_pow) == "2F1":
  102. return {'I0':I0, 'k':k, 'sing_point':sing_point, 'type':"2F1"}
  103. else:
  104. return None
  105. def match_2nd_2F1_hypergeometric(I, k, sing_point, func):
  106. x = func.args[0]
  107. a = Wild("a")
  108. b = Wild("b")
  109. c = Wild("c")
  110. t = Wild("t")
  111. s = Wild("s")
  112. r = Wild("r")
  113. alpha = Wild("alpha")
  114. beta = Wild("beta")
  115. gamma = Wild("gamma")
  116. delta = Wild("delta")
  117. # I0 of the standerd 2F1 equation.
  118. 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)
  119. if sing_point != [0, 1]:
  120. # If singular point is [0, 1] then we have standerd equation.
  121. eqs = []
  122. sing_eqs = [-beta/alpha, -delta/gamma, (delta-beta)/(alpha-gamma)]
  123. # making equations for the finding the mobius transformation
  124. for i in range(3):
  125. if i<len(sing_point):
  126. eqs.append(Eq(sing_eqs[i], sing_point[i]))
  127. else:
  128. eqs.append(Eq(1/sing_eqs[i], 0))
  129. # solving above equations for the mobius transformation
  130. _beta = -alpha*sing_point[0]
  131. _delta = -gamma*sing_point[1]
  132. _gamma = alpha
  133. if len(sing_point) == 3:
  134. _gamma = (_beta + sing_point[2]*alpha)/(sing_point[2] - sing_point[1])
  135. mob = (alpha*x + beta)/(gamma*x + delta)
  136. mob = mob.subs(beta, _beta)
  137. mob = mob.subs(delta, _delta)
  138. mob = mob.subs(gamma, _gamma)
  139. mob = cancel(mob)
  140. t = (beta - delta*x)/(gamma*x - alpha)
  141. t = cancel(((t.subs(beta, _beta)).subs(delta, _delta)).subs(gamma, _gamma))
  142. else:
  143. mob = x
  144. t = x
  145. # applying mobius transformation in I to make it into I0.
  146. I = I.subs(x, t)
  147. I = I*(t.diff(x))**2
  148. I = factor(I)
  149. dict_I = {x**2:0, x:0, 1:0}
  150. I0_num, I0_dem = I0.as_numer_denom()
  151. # collecting coeff of (x**2, x), of the standerd equation.
  152. # substituting (a-b) = s, (a+b) = r
  153. dict_I0 = {x**2:s**2 - 1, x:(2*(1-r)*c + (r+s)*(r-s)), 1:c*(c-2)}
  154. # collecting coeff of (x**2, x) from I0 of the given equation.
  155. dict_I.update(collect(expand(cancel(I*I0_dem)), [x**2, x], evaluate=False))
  156. eqs = []
  157. # We are comparing the coeff of powers of different x, for finding the values of
  158. # parameters of standerd equation.
  159. for key in [x**2, x, 1]:
  160. eqs.append(Eq(dict_I[key], dict_I0[key]))
  161. # We can have many possible roots for the equation.
  162. # I am selecting the root on the basis that when we have
  163. # standard equation eq = x*(x-1)*f(x).diff(x, 2) + ((a+b+1)*x-c)*f(x).diff(x) + a*b*f(x)
  164. # then root should be a, b, c.
  165. _c = 1 - factor(sqrt(1+eqs[2].lhs))
  166. if not _c.has(Symbol):
  167. _c = min(list(roots(eqs[2], c)))
  168. _s = factor(sqrt(eqs[0].lhs + 1))
  169. _r = _c - factor(sqrt(_c**2 + _s**2 + eqs[1].lhs - 2*_c))
  170. _a = (_r + _s)/2
  171. _b = (_r - _s)/2
  172. rn = {'a':simplify(_a), 'b':simplify(_b), 'c':simplify(_c), 'k':k, 'mobius':mob, 'type':"2F1"}
  173. return rn
  174. def equivalence(max_num_pow, dem_pow):
  175. # this function is made for checking the equivalence with 2F1 type of equation.
  176. # max_num_pow is the value of maximum power of x in numerator
  177. # and dem_pow is list of powers of different factor of form (a*x b).
  178. # reference from table 1 in paper - "Non-Liouvillian solutions for second order
  179. # linear ODEs" by L. Chan, E.S. Cheb-Terrab.
  180. # We can extend it for 1F1 and 0F1 type also.
  181. if max_num_pow == 2:
  182. if dem_pow in [[2, 2], [2, 2, 2]]:
  183. return "2F1"
  184. elif max_num_pow == 1:
  185. if dem_pow in [[1, 2, 2], [2, 2, 2], [1, 2], [2, 2]]:
  186. return "2F1"
  187. elif max_num_pow == 0:
  188. if dem_pow in [[1, 1, 2], [2, 2], [1, 2, 2], [1, 1], [2], [1, 2], [2, 2]]:
  189. return "2F1"
  190. return None
  191. def get_sol_2F1_hypergeometric(eq, func, match_object):
  192. x = func.args[0]
  193. from sympy.simplify.hyperexpand import hyperexpand
  194. from sympy.polys.polytools import factor
  195. C0, C1 = get_numbered_constants(eq, num=2)
  196. a = match_object['a']
  197. b = match_object['b']
  198. c = match_object['c']
  199. A = match_object['A']
  200. sol = None
  201. if c.is_integer == False:
  202. sol = C0*hyper([a, b], [c], x) + C1*hyper([a-c+1, b-c+1], [2-c], x)*x**(1-c)
  203. elif c == 1:
  204. 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)
  205. sol = C0*hyper([a, b], [c], x) + C1*y2
  206. elif (c-a-b).is_integer == False:
  207. 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)
  208. if sol:
  209. # applying transformation in the solution
  210. subs = match_object['mobius']
  211. dtdx = simplify(1/(subs.diff(x)))
  212. _B = ((a + b + 1)*x - c).subs(x, subs)*dtdx
  213. _B = factor(_B + ((x**2 -x).subs(x, subs))*(dtdx.diff(x)*dtdx))
  214. _A = factor((x**2 - x).subs(x, subs)*(dtdx**2))
  215. e = exp(logcombine(Integral(cancel(_B/(2*_A)), x), force=True))
  216. sol = sol.subs(x, match_object['mobius'])
  217. sol = sol.subs(x, x**match_object['k'])
  218. e = e.subs(x, x**match_object['k'])
  219. if not A.is_zero:
  220. e1 = Integral(A/2, x)
  221. e1 = exp(logcombine(e1, force=True))
  222. sol = cancel((e/e1)*x**((-match_object['k']+1)/2))*sol
  223. sol = Eq(func, sol)
  224. return sol
  225. sol = cancel((e)*x**((-match_object['k']+1)/2))*sol
  226. sol = Eq(func, sol)
  227. return sol