holonomic.py 90 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765
  1. """
  2. This module implements Holonomic Functions and
  3. various operations on them.
  4. """
  5. from sympy.core import Add, Mul, Pow
  6. from sympy.core.numbers import (NaN, Infinity, NegativeInfinity, Float, I, pi,
  7. equal_valued, int_valued)
  8. from sympy.core.singleton import S
  9. from sympy.core.sorting import ordered
  10. from sympy.core.symbol import Dummy, Symbol
  11. from sympy.core.sympify import sympify
  12. from sympy.functions.combinatorial.factorials import binomial, factorial, rf
  13. from sympy.functions.elementary.exponential import exp_polar, exp, log
  14. from sympy.functions.elementary.hyperbolic import (cosh, sinh)
  15. from sympy.functions.elementary.miscellaneous import sqrt
  16. from sympy.functions.elementary.trigonometric import (cos, sin, sinc)
  17. from sympy.functions.special.error_functions import (Ci, Shi, Si, erf, erfc, erfi)
  18. from sympy.functions.special.gamma_functions import gamma
  19. from sympy.functions.special.hyper import hyper, meijerg
  20. from sympy.integrals import meijerint
  21. from sympy.matrices import Matrix
  22. from sympy.polys.rings import PolyElement
  23. from sympy.polys.fields import FracElement
  24. from sympy.polys.domains import QQ, RR
  25. from sympy.polys.polyclasses import DMF
  26. from sympy.polys.polyroots import roots
  27. from sympy.polys.polytools import Poly
  28. from sympy.polys.matrices import DomainMatrix
  29. from sympy.printing import sstr
  30. from sympy.series.limits import limit
  31. from sympy.series.order import Order
  32. from sympy.simplify.hyperexpand import hyperexpand
  33. from sympy.simplify.simplify import nsimplify
  34. from sympy.solvers.solvers import solve
  35. from .recurrence import HolonomicSequence, RecurrenceOperator, RecurrenceOperators
  36. from .holonomicerrors import (NotPowerSeriesError, NotHyperSeriesError,
  37. SingularityError, NotHolonomicError)
  38. def _find_nonzero_solution(r, homosys):
  39. ones = lambda shape: DomainMatrix.ones(shape, r.domain)
  40. particular, nullspace = r._solve(homosys)
  41. nullity = nullspace.shape[0]
  42. nullpart = ones((1, nullity)) * nullspace
  43. sol = (particular + nullpart).transpose()
  44. return sol
  45. def DifferentialOperators(base, generator):
  46. r"""
  47. This function is used to create annihilators using ``Dx``.
  48. Explanation
  49. ===========
  50. Returns an Algebra of Differential Operators also called Weyl Algebra
  51. and the operator for differentiation i.e. the ``Dx`` operator.
  52. Parameters
  53. ==========
  54. base:
  55. Base polynomial ring for the algebra.
  56. The base polynomial ring is the ring of polynomials in :math:`x` that
  57. will appear as coefficients in the operators.
  58. generator:
  59. Generator of the algebra which can
  60. be either a noncommutative ``Symbol`` or a string. e.g. "Dx" or "D".
  61. Examples
  62. ========
  63. >>> from sympy import ZZ
  64. >>> from sympy.abc import x
  65. >>> from sympy.holonomic.holonomic import DifferentialOperators
  66. >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx')
  67. >>> R
  68. Univariate Differential Operator Algebra in intermediate Dx over the base ring ZZ[x]
  69. >>> Dx*x
  70. (1) + (x)*Dx
  71. """
  72. ring = DifferentialOperatorAlgebra(base, generator)
  73. return (ring, ring.derivative_operator)
  74. class DifferentialOperatorAlgebra:
  75. r"""
  76. An Ore Algebra is a set of noncommutative polynomials in the
  77. intermediate ``Dx`` and coefficients in a base polynomial ring :math:`A`.
  78. It follows the commutation rule:
  79. .. math ::
  80. Dxa = \sigma(a)Dx + \delta(a)
  81. for :math:`a \subset A`.
  82. Where :math:`\sigma: A \Rightarrow A` is an endomorphism and :math:`\delta: A \rightarrow A`
  83. is a skew-derivation i.e. :math:`\delta(ab) = \delta(a) b + \sigma(a) \delta(b)`.
  84. If one takes the sigma as identity map and delta as the standard derivation
  85. then it becomes the algebra of Differential Operators also called
  86. a Weyl Algebra i.e. an algebra whose elements are Differential Operators.
  87. This class represents a Weyl Algebra and serves as the parent ring for
  88. Differential Operators.
  89. Examples
  90. ========
  91. >>> from sympy import ZZ
  92. >>> from sympy import symbols
  93. >>> from sympy.holonomic.holonomic import DifferentialOperators
  94. >>> x = symbols('x')
  95. >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx')
  96. >>> R
  97. Univariate Differential Operator Algebra in intermediate Dx over the base ring
  98. ZZ[x]
  99. See Also
  100. ========
  101. DifferentialOperator
  102. """
  103. def __init__(self, base, generator):
  104. # the base polynomial ring for the algebra
  105. self.base = base
  106. # the operator representing differentiation i.e. `Dx`
  107. self.derivative_operator = DifferentialOperator(
  108. [base.zero, base.one], self)
  109. if generator is None:
  110. self.gen_symbol = Symbol('Dx', commutative=False)
  111. else:
  112. if isinstance(generator, str):
  113. self.gen_symbol = Symbol(generator, commutative=False)
  114. elif isinstance(generator, Symbol):
  115. self.gen_symbol = generator
  116. def __str__(self):
  117. string = 'Univariate Differential Operator Algebra in intermediate '\
  118. + sstr(self.gen_symbol) + ' over the base ring ' + \
  119. (self.base).__str__()
  120. return string
  121. __repr__ = __str__
  122. def __eq__(self, other):
  123. return self.base == other.base and \
  124. self.gen_symbol == other.gen_symbol
  125. class DifferentialOperator:
  126. """
  127. Differential Operators are elements of Weyl Algebra. The Operators
  128. are defined by a list of polynomials in the base ring and the
  129. parent ring of the Operator i.e. the algebra it belongs to.
  130. Explanation
  131. ===========
  132. Takes a list of polynomials for each power of ``Dx`` and the
  133. parent ring which must be an instance of DifferentialOperatorAlgebra.
  134. A Differential Operator can be created easily using
  135. the operator ``Dx``. See examples below.
  136. Examples
  137. ========
  138. >>> from sympy.holonomic.holonomic import DifferentialOperator, DifferentialOperators
  139. >>> from sympy import ZZ
  140. >>> from sympy import symbols
  141. >>> x = symbols('x')
  142. >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx')
  143. >>> DifferentialOperator([0, 1, x**2], R)
  144. (1)*Dx + (x**2)*Dx**2
  145. >>> (x*Dx*x + 1 - Dx**2)**2
  146. (2*x**2 + 2*x + 1) + (4*x**3 + 2*x**2 - 4)*Dx + (x**4 - 6*x - 2)*Dx**2 + (-2*x**2)*Dx**3 + (1)*Dx**4
  147. See Also
  148. ========
  149. DifferentialOperatorAlgebra
  150. """
  151. _op_priority = 20
  152. def __init__(self, list_of_poly, parent):
  153. """
  154. Parameters
  155. ==========
  156. list_of_poly:
  157. List of polynomials belonging to the base ring of the algebra.
  158. parent:
  159. Parent algebra of the operator.
  160. """
  161. # the parent ring for this operator
  162. # must be an DifferentialOperatorAlgebra object
  163. self.parent = parent
  164. base = self.parent.base
  165. self.x = base.gens[0] if isinstance(base.gens[0], Symbol) else base.gens[0][0]
  166. # sequence of polynomials in x for each power of Dx
  167. # the list should not have trailing zeroes
  168. # represents the operator
  169. # convert the expressions into ring elements using from_sympy
  170. for i, j in enumerate(list_of_poly):
  171. if not isinstance(j, base.dtype):
  172. list_of_poly[i] = base.from_sympy(sympify(j))
  173. else:
  174. list_of_poly[i] = base.from_sympy(base.to_sympy(j))
  175. self.listofpoly = list_of_poly
  176. # highest power of `Dx`
  177. self.order = len(self.listofpoly) - 1
  178. def __mul__(self, other):
  179. """
  180. Multiplies two DifferentialOperator and returns another
  181. DifferentialOperator instance using the commutation rule
  182. Dx*a = a*Dx + a'
  183. """
  184. listofself = self.listofpoly
  185. if isinstance(other, DifferentialOperator):
  186. listofother = other.listofpoly
  187. elif isinstance(other, self.parent.base.dtype):
  188. listofother = [other]
  189. else:
  190. listofother = [self.parent.base.from_sympy(sympify(other))]
  191. # multiplies a polynomial `b` with a list of polynomials
  192. def _mul_dmp_diffop(b, listofother):
  193. if isinstance(listofother, list):
  194. return [i * b for i in listofother]
  195. return [b * listofother]
  196. sol = _mul_dmp_diffop(listofself[0], listofother)
  197. # compute Dx^i * b
  198. def _mul_Dxi_b(b):
  199. sol1 = [self.parent.base.zero]
  200. sol2 = []
  201. if isinstance(b, list):
  202. for i in b:
  203. sol1.append(i)
  204. sol2.append(i.diff())
  205. else:
  206. sol1.append(self.parent.base.from_sympy(b))
  207. sol2.append(self.parent.base.from_sympy(b).diff())
  208. return _add_lists(sol1, sol2)
  209. for i in range(1, len(listofself)):
  210. # find Dx^i * b in ith iteration
  211. listofother = _mul_Dxi_b(listofother)
  212. # solution = solution + listofself[i] * (Dx^i * b)
  213. sol = _add_lists(sol, _mul_dmp_diffop(listofself[i], listofother))
  214. return DifferentialOperator(sol, self.parent)
  215. def __rmul__(self, other):
  216. if not isinstance(other, DifferentialOperator):
  217. if not isinstance(other, self.parent.base.dtype):
  218. other = (self.parent.base).from_sympy(sympify(other))
  219. sol = [other * j for j in self.listofpoly]
  220. return DifferentialOperator(sol, self.parent)
  221. def __add__(self, other):
  222. if isinstance(other, DifferentialOperator):
  223. sol = _add_lists(self.listofpoly, other.listofpoly)
  224. return DifferentialOperator(sol, self.parent)
  225. list_self = self.listofpoly
  226. if not isinstance(other, self.parent.base.dtype):
  227. list_other = [((self.parent).base).from_sympy(sympify(other))]
  228. else:
  229. list_other = [other]
  230. sol = [list_self[0] + list_other[0]] + list_self[1:]
  231. return DifferentialOperator(sol, self.parent)
  232. __radd__ = __add__
  233. def __sub__(self, other):
  234. return self + (-1) * other
  235. def __rsub__(self, other):
  236. return (-1) * self + other
  237. def __neg__(self):
  238. return -1 * self
  239. def __truediv__(self, other):
  240. return self * (S.One / other)
  241. def __pow__(self, n):
  242. if n == 1:
  243. return self
  244. result = DifferentialOperator([self.parent.base.one], self.parent)
  245. if n == 0:
  246. return result
  247. # if self is `Dx`
  248. if self.listofpoly == self.parent.derivative_operator.listofpoly:
  249. sol = [self.parent.base.zero]*n + [self.parent.base.one]
  250. return DifferentialOperator(sol, self.parent)
  251. x = self
  252. while True:
  253. if n % 2:
  254. result *= x
  255. n >>= 1
  256. if not n:
  257. break
  258. x *= x
  259. return result
  260. def __str__(self):
  261. listofpoly = self.listofpoly
  262. print_str = ''
  263. for i, j in enumerate(listofpoly):
  264. if j == self.parent.base.zero:
  265. continue
  266. j = self.parent.base.to_sympy(j)
  267. if i == 0:
  268. print_str += '(' + sstr(j) + ')'
  269. continue
  270. if print_str:
  271. print_str += ' + '
  272. if i == 1:
  273. print_str += '(' + sstr(j) + ')*%s' %(self.parent.gen_symbol)
  274. continue
  275. print_str += '(' + sstr(j) + ')' + '*%s**' %(self.parent.gen_symbol) + sstr(i)
  276. return print_str
  277. __repr__ = __str__
  278. def __eq__(self, other):
  279. if isinstance(other, DifferentialOperator):
  280. return self.listofpoly == other.listofpoly and \
  281. self.parent == other.parent
  282. return self.listofpoly[0] == other and \
  283. all(i is self.parent.base.zero for i in self.listofpoly[1:])
  284. def is_singular(self, x0):
  285. """
  286. Checks if the differential equation is singular at x0.
  287. """
  288. base = self.parent.base
  289. return x0 in roots(base.to_sympy(self.listofpoly[-1]), self.x)
  290. class HolonomicFunction:
  291. r"""
  292. A Holonomic Function is a solution to a linear homogeneous ordinary
  293. differential equation with polynomial coefficients. This differential
  294. equation can also be represented by an annihilator i.e. a Differential
  295. Operator ``L`` such that :math:`L.f = 0`. For uniqueness of these functions,
  296. initial conditions can also be provided along with the annihilator.
  297. Explanation
  298. ===========
  299. Holonomic functions have closure properties and thus forms a ring.
  300. Given two Holonomic Functions f and g, their sum, product,
  301. integral and derivative is also a Holonomic Function.
  302. For ordinary points initial condition should be a vector of values of
  303. the derivatives i.e. :math:`[y(x_0), y'(x_0), y''(x_0) ... ]`.
  304. For regular singular points initial conditions can also be provided in this
  305. format:
  306. :math:`{s0: [C_0, C_1, ...], s1: [C^1_0, C^1_1, ...], ...}`
  307. where s0, s1, ... are the roots of indicial equation and vectors
  308. :math:`[C_0, C_1, ...], [C^0_0, C^0_1, ...], ...` are the corresponding initial
  309. terms of the associated power series. See Examples below.
  310. Examples
  311. ========
  312. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  313. >>> from sympy import QQ
  314. >>> from sympy import symbols, S
  315. >>> x = symbols('x')
  316. >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx')
  317. >>> p = HolonomicFunction(Dx - 1, x, 0, [1]) # e^x
  318. >>> q = HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]) # sin(x)
  319. >>> p + q # annihilator of e^x + sin(x)
  320. HolonomicFunction((-1) + (1)*Dx + (-1)*Dx**2 + (1)*Dx**3, x, 0, [1, 2, 1])
  321. >>> p * q # annihilator of e^x * sin(x)
  322. HolonomicFunction((2) + (-2)*Dx + (1)*Dx**2, x, 0, [0, 1])
  323. An example of initial conditions for regular singular points,
  324. the indicial equation has only one root `1/2`.
  325. >>> HolonomicFunction(-S(1)/2 + x*Dx, x, 0, {S(1)/2: [1]})
  326. HolonomicFunction((-1/2) + (x)*Dx, x, 0, {1/2: [1]})
  327. >>> HolonomicFunction(-S(1)/2 + x*Dx, x, 0, {S(1)/2: [1]}).to_expr()
  328. sqrt(x)
  329. To plot a Holonomic Function, one can use `.evalf()` for numerical
  330. computation. Here's an example on `sin(x)**2/x` using numpy and matplotlib.
  331. >>> import sympy.holonomic # doctest: +SKIP
  332. >>> from sympy import var, sin # doctest: +SKIP
  333. >>> import matplotlib.pyplot as plt # doctest: +SKIP
  334. >>> import numpy as np # doctest: +SKIP
  335. >>> var("x") # doctest: +SKIP
  336. >>> r = np.linspace(1, 5, 100) # doctest: +SKIP
  337. >>> y = sympy.holonomic.expr_to_holonomic(sin(x)**2/x, x0=1).evalf(r) # doctest: +SKIP
  338. >>> plt.plot(r, y, label="holonomic function") # doctest: +SKIP
  339. >>> plt.show() # doctest: +SKIP
  340. """
  341. _op_priority = 20
  342. def __init__(self, annihilator, x, x0=0, y0=None):
  343. """
  344. Parameters
  345. ==========
  346. annihilator:
  347. Annihilator of the Holonomic Function, represented by a
  348. `DifferentialOperator` object.
  349. x:
  350. Variable of the function.
  351. x0:
  352. The point at which initial conditions are stored.
  353. Generally an integer.
  354. y0:
  355. The initial condition. The proper format for the initial condition
  356. is described in class docstring. To make the function unique,
  357. length of the vector `y0` should be equal to or greater than the
  358. order of differential equation.
  359. """
  360. # initial condition
  361. self.y0 = y0
  362. # the point for initial conditions, default is zero.
  363. self.x0 = x0
  364. # differential operator L such that L.f = 0
  365. self.annihilator = annihilator
  366. self.x = x
  367. def __str__(self):
  368. if self._have_init_cond():
  369. str_sol = 'HolonomicFunction(%s, %s, %s, %s)' % (str(self.annihilator),\
  370. sstr(self.x), sstr(self.x0), sstr(self.y0))
  371. else:
  372. str_sol = 'HolonomicFunction(%s, %s)' % (str(self.annihilator),\
  373. sstr(self.x))
  374. return str_sol
  375. __repr__ = __str__
  376. def unify(self, other):
  377. """
  378. Unifies the base polynomial ring of a given two Holonomic
  379. Functions.
  380. """
  381. R1 = self.annihilator.parent.base
  382. R2 = other.annihilator.parent.base
  383. dom1 = R1.dom
  384. dom2 = R2.dom
  385. if R1 == R2:
  386. return (self, other)
  387. R = (dom1.unify(dom2)).old_poly_ring(self.x)
  388. newparent, _ = DifferentialOperators(R, str(self.annihilator.parent.gen_symbol))
  389. sol1 = [R1.to_sympy(i) for i in self.annihilator.listofpoly]
  390. sol2 = [R2.to_sympy(i) for i in other.annihilator.listofpoly]
  391. sol1 = DifferentialOperator(sol1, newparent)
  392. sol2 = DifferentialOperator(sol2, newparent)
  393. sol1 = HolonomicFunction(sol1, self.x, self.x0, self.y0)
  394. sol2 = HolonomicFunction(sol2, other.x, other.x0, other.y0)
  395. return (sol1, sol2)
  396. def is_singularics(self):
  397. """
  398. Returns True if the function have singular initial condition
  399. in the dictionary format.
  400. Returns False if the function have ordinary initial condition
  401. in the list format.
  402. Returns None for all other cases.
  403. """
  404. if isinstance(self.y0, dict):
  405. return True
  406. elif isinstance(self.y0, list):
  407. return False
  408. def _have_init_cond(self):
  409. """
  410. Checks if the function have initial condition.
  411. """
  412. return bool(self.y0)
  413. def _singularics_to_ord(self):
  414. """
  415. Converts a singular initial condition to ordinary if possible.
  416. """
  417. a = list(self.y0)[0]
  418. b = self.y0[a]
  419. if len(self.y0) == 1 and a == int(a) and a > 0:
  420. a = int(a)
  421. y0 = [S.Zero] * a
  422. y0 += [j * factorial(a + i) for i, j in enumerate(b)]
  423. return HolonomicFunction(self.annihilator, self.x, self.x0, y0)
  424. def __add__(self, other):
  425. # if the ground domains are different
  426. if self.annihilator.parent.base != other.annihilator.parent.base:
  427. a, b = self.unify(other)
  428. return a + b
  429. deg1 = self.annihilator.order
  430. deg2 = other.annihilator.order
  431. dim = max(deg1, deg2)
  432. R = self.annihilator.parent.base
  433. K = R.get_field()
  434. rowsself = [self.annihilator]
  435. rowsother = [other.annihilator]
  436. gen = self.annihilator.parent.derivative_operator
  437. # constructing annihilators up to order dim
  438. for i in range(dim - deg1):
  439. diff1 = (gen * rowsself[-1])
  440. rowsself.append(diff1)
  441. for i in range(dim - deg2):
  442. diff2 = (gen * rowsother[-1])
  443. rowsother.append(diff2)
  444. row = rowsself + rowsother
  445. # constructing the matrix of the ansatz
  446. r = []
  447. for expr in row:
  448. p = []
  449. for i in range(dim + 1):
  450. if i >= len(expr.listofpoly):
  451. p.append(K.zero)
  452. else:
  453. p.append(K.new(expr.listofpoly[i].to_list()))
  454. r.append(p)
  455. # solving the linear system using gauss jordan solver
  456. r = DomainMatrix(r, (len(row), dim+1), K).transpose()
  457. homosys = DomainMatrix.zeros((dim+1, 1), K)
  458. sol = _find_nonzero_solution(r, homosys)
  459. # if a solution is not obtained then increasing the order by 1 in each
  460. # iteration
  461. while sol.is_zero_matrix:
  462. dim += 1
  463. diff1 = (gen * rowsself[-1])
  464. rowsself.append(diff1)
  465. diff2 = (gen * rowsother[-1])
  466. rowsother.append(diff2)
  467. row = rowsself + rowsother
  468. r = []
  469. for expr in row:
  470. p = []
  471. for i in range(dim + 1):
  472. if i >= len(expr.listofpoly):
  473. p.append(K.zero)
  474. else:
  475. p.append(K.new(expr.listofpoly[i].to_list()))
  476. r.append(p)
  477. # solving the linear system using gauss jordan solver
  478. r = DomainMatrix(r, (len(row), dim+1), K).transpose()
  479. homosys = DomainMatrix.zeros((dim+1, 1), K)
  480. sol = _find_nonzero_solution(r, homosys)
  481. # taking only the coefficients needed to multiply with `self`
  482. # can be also be done the other way by taking R.H.S and multiplying with
  483. # `other`
  484. sol = sol.flat()[:dim + 1 - deg1]
  485. sol1 = _normalize(sol, self.annihilator.parent)
  486. # annihilator of the solution
  487. sol = sol1 * (self.annihilator)
  488. sol = _normalize(sol.listofpoly, self.annihilator.parent, negative=False)
  489. if not (self._have_init_cond() and other._have_init_cond()):
  490. return HolonomicFunction(sol, self.x)
  491. # both the functions have ordinary initial conditions
  492. if self.is_singularics() == False and other.is_singularics() == False:
  493. # directly add the corresponding value
  494. if self.x0 == other.x0:
  495. # try to extended the initial conditions
  496. # using the annihilator
  497. y1 = _extend_y0(self, sol.order)
  498. y2 = _extend_y0(other, sol.order)
  499. y0 = [a + b for a, b in zip(y1, y2)]
  500. return HolonomicFunction(sol, self.x, self.x0, y0)
  501. # change the initial conditions to a same point
  502. selfat0 = self.annihilator.is_singular(0)
  503. otherat0 = other.annihilator.is_singular(0)
  504. if self.x0 == 0 and not selfat0 and not otherat0:
  505. return self + other.change_ics(0)
  506. if other.x0 == 0 and not selfat0 and not otherat0:
  507. return self.change_ics(0) + other
  508. selfatx0 = self.annihilator.is_singular(self.x0)
  509. otheratx0 = other.annihilator.is_singular(self.x0)
  510. if not selfatx0 and not otheratx0:
  511. return self + other.change_ics(self.x0)
  512. return self.change_ics(other.x0) + other
  513. if self.x0 != other.x0:
  514. return HolonomicFunction(sol, self.x)
  515. # if the functions have singular_ics
  516. y1 = None
  517. y2 = None
  518. if self.is_singularics() == False and other.is_singularics() == True:
  519. # convert the ordinary initial condition to singular.
  520. _y0 = [j / factorial(i) for i, j in enumerate(self.y0)]
  521. y1 = {S.Zero: _y0}
  522. y2 = other.y0
  523. elif self.is_singularics() == True and other.is_singularics() == False:
  524. _y0 = [j / factorial(i) for i, j in enumerate(other.y0)]
  525. y1 = self.y0
  526. y2 = {S.Zero: _y0}
  527. elif self.is_singularics() == True and other.is_singularics() == True:
  528. y1 = self.y0
  529. y2 = other.y0
  530. # computing singular initial condition for the result
  531. # taking union of the series terms of both functions
  532. y0 = {}
  533. for i in y1:
  534. # add corresponding initial terms if the power
  535. # on `x` is same
  536. if i in y2:
  537. y0[i] = [a + b for a, b in zip(y1[i], y2[i])]
  538. else:
  539. y0[i] = y1[i]
  540. for i in y2:
  541. if i not in y1:
  542. y0[i] = y2[i]
  543. return HolonomicFunction(sol, self.x, self.x0, y0)
  544. def integrate(self, limits, initcond=False):
  545. """
  546. Integrates the given holonomic function.
  547. Examples
  548. ========
  549. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  550. >>> from sympy import QQ
  551. >>> from sympy import symbols
  552. >>> x = symbols('x')
  553. >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx')
  554. >>> HolonomicFunction(Dx - 1, x, 0, [1]).integrate((x, 0, x)) # e^x - 1
  555. HolonomicFunction((-1)*Dx + (1)*Dx**2, x, 0, [0, 1])
  556. >>> HolonomicFunction(Dx**2 + 1, x, 0, [1, 0]).integrate((x, 0, x))
  557. HolonomicFunction((1)*Dx + (1)*Dx**3, x, 0, [0, 1, 0])
  558. """
  559. # to get the annihilator, just multiply by Dx from right
  560. D = self.annihilator.parent.derivative_operator
  561. # if the function have initial conditions of the series format
  562. if self.is_singularics() == True:
  563. r = self._singularics_to_ord()
  564. if r:
  565. return r.integrate(limits, initcond=initcond)
  566. # computing singular initial condition for the function
  567. # produced after integration.
  568. y0 = {}
  569. for i in self.y0:
  570. c = self.y0[i]
  571. c2 = []
  572. for j, cj in enumerate(c):
  573. if cj == 0:
  574. c2.append(S.Zero)
  575. # if power on `x` is -1, the integration becomes log(x)
  576. # TODO: Implement this case
  577. elif i + j + 1 == 0:
  578. raise NotImplementedError("logarithmic terms in the series are not supported")
  579. else:
  580. c2.append(cj / S(i + j + 1))
  581. y0[i + 1] = c2
  582. if hasattr(limits, "__iter__"):
  583. raise NotImplementedError("Definite integration for singular initial conditions")
  584. return HolonomicFunction(self.annihilator * D, self.x, self.x0, y0)
  585. # if no initial conditions are available for the function
  586. if not self._have_init_cond():
  587. if initcond:
  588. return HolonomicFunction(self.annihilator * D, self.x, self.x0, [S.Zero])
  589. return HolonomicFunction(self.annihilator * D, self.x)
  590. # definite integral
  591. # initial conditions for the answer will be stored at point `a`,
  592. # where `a` is the lower limit of the integrand
  593. if hasattr(limits, "__iter__"):
  594. if len(limits) == 3 and limits[0] == self.x:
  595. x0 = self.x0
  596. a = limits[1]
  597. b = limits[2]
  598. definite = True
  599. else:
  600. definite = False
  601. y0 = [S.Zero]
  602. y0 += self.y0
  603. indefinite_integral = HolonomicFunction(self.annihilator * D, self.x, self.x0, y0)
  604. if not definite:
  605. return indefinite_integral
  606. # use evalf to get the values at `a`
  607. if x0 != a:
  608. try:
  609. indefinite_expr = indefinite_integral.to_expr()
  610. except (NotHyperSeriesError, NotPowerSeriesError):
  611. indefinite_expr = None
  612. if indefinite_expr:
  613. lower = indefinite_expr.subs(self.x, a)
  614. if isinstance(lower, NaN):
  615. lower = indefinite_expr.limit(self.x, a)
  616. else:
  617. lower = indefinite_integral.evalf(a)
  618. if b == self.x:
  619. y0[0] = y0[0] - lower
  620. return HolonomicFunction(self.annihilator * D, self.x, x0, y0)
  621. elif S(b).is_Number:
  622. if indefinite_expr:
  623. upper = indefinite_expr.subs(self.x, b)
  624. if isinstance(upper, NaN):
  625. upper = indefinite_expr.limit(self.x, b)
  626. else:
  627. upper = indefinite_integral.evalf(b)
  628. return upper - lower
  629. # if the upper limit is `x`, the answer will be a function
  630. if b == self.x:
  631. return HolonomicFunction(self.annihilator * D, self.x, a, y0)
  632. # if the upper limits is a Number, a numerical value will be returned
  633. elif S(b).is_Number:
  634. try:
  635. s = HolonomicFunction(self.annihilator * D, self.x, a,\
  636. y0).to_expr()
  637. indefinite = s.subs(self.x, b)
  638. if not isinstance(indefinite, NaN):
  639. return indefinite
  640. else:
  641. return s.limit(self.x, b)
  642. except (NotHyperSeriesError, NotPowerSeriesError):
  643. return HolonomicFunction(self.annihilator * D, self.x, a, y0).evalf(b)
  644. return HolonomicFunction(self.annihilator * D, self.x)
  645. def diff(self, *args, **kwargs):
  646. r"""
  647. Differentiation of the given Holonomic function.
  648. Examples
  649. ========
  650. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  651. >>> from sympy import ZZ
  652. >>> from sympy import symbols
  653. >>> x = symbols('x')
  654. >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx')
  655. >>> HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]).diff().to_expr()
  656. cos(x)
  657. >>> HolonomicFunction(Dx - 2, x, 0, [1]).diff().to_expr()
  658. 2*exp(2*x)
  659. See Also
  660. ========
  661. integrate
  662. """
  663. kwargs.setdefault('evaluate', True)
  664. if args:
  665. if args[0] != self.x:
  666. return S.Zero
  667. elif len(args) == 2:
  668. sol = self
  669. for i in range(args[1]):
  670. sol = sol.diff(args[0])
  671. return sol
  672. ann = self.annihilator
  673. # if the function is constant.
  674. if ann.listofpoly[0] == ann.parent.base.zero and ann.order == 1:
  675. return S.Zero
  676. # if the coefficient of y in the differential equation is zero.
  677. # a shifting is done to compute the answer in this case.
  678. elif ann.listofpoly[0] == ann.parent.base.zero:
  679. sol = DifferentialOperator(ann.listofpoly[1:], ann.parent)
  680. if self._have_init_cond():
  681. # if ordinary initial condition
  682. if self.is_singularics() == False:
  683. return HolonomicFunction(sol, self.x, self.x0, self.y0[1:])
  684. # TODO: support for singular initial condition
  685. return HolonomicFunction(sol, self.x)
  686. else:
  687. return HolonomicFunction(sol, self.x)
  688. # the general algorithm
  689. R = ann.parent.base
  690. K = R.get_field()
  691. seq_dmf = [K.new(i.to_list()) for i in ann.listofpoly]
  692. # -y = a1*y'/a0 + a2*y''/a0 ... + an*y^n/a0
  693. rhs = [i / seq_dmf[0] for i in seq_dmf[1:]]
  694. rhs.insert(0, K.zero)
  695. # differentiate both lhs and rhs
  696. sol = _derivate_diff_eq(rhs, K)
  697. # add the term y' in lhs to rhs
  698. sol = _add_lists(sol, [K.zero, K.one])
  699. sol = _normalize(sol[1:], self.annihilator.parent, negative=False)
  700. if not self._have_init_cond() or self.is_singularics() == True:
  701. return HolonomicFunction(sol, self.x)
  702. y0 = _extend_y0(self, sol.order + 1)[1:]
  703. return HolonomicFunction(sol, self.x, self.x0, y0)
  704. def __eq__(self, other):
  705. if self.annihilator != other.annihilator or self.x != other.x:
  706. return False
  707. if self._have_init_cond() and other._have_init_cond():
  708. return self.x0 == other.x0 and self.y0 == other.y0
  709. return True
  710. def __mul__(self, other):
  711. ann_self = self.annihilator
  712. if not isinstance(other, HolonomicFunction):
  713. other = sympify(other)
  714. if other.has(self.x):
  715. raise NotImplementedError(" Can't multiply a HolonomicFunction and expressions/functions.")
  716. if not self._have_init_cond():
  717. return self
  718. y0 = _extend_y0(self, ann_self.order)
  719. y1 = [(Poly.new(j, self.x) * other).rep for j in y0]
  720. return HolonomicFunction(ann_self, self.x, self.x0, y1)
  721. if self.annihilator.parent.base != other.annihilator.parent.base:
  722. a, b = self.unify(other)
  723. return a * b
  724. ann_other = other.annihilator
  725. a = ann_self.order
  726. b = ann_other.order
  727. R = ann_self.parent.base
  728. K = R.get_field()
  729. list_self = [K.new(j.to_list()) for j in ann_self.listofpoly]
  730. list_other = [K.new(j.to_list()) for j in ann_other.listofpoly]
  731. # will be used to reduce the degree
  732. self_red = [-list_self[i] / list_self[a] for i in range(a)]
  733. other_red = [-list_other[i] / list_other[b] for i in range(b)]
  734. # coeff_mull[i][j] is the coefficient of Dx^i(f).Dx^j(g)
  735. coeff_mul = [[K.zero for i in range(b + 1)] for j in range(a + 1)]
  736. coeff_mul[0][0] = K.one
  737. # making the ansatz
  738. lin_sys_elements = [[coeff_mul[i][j] for i in range(a) for j in range(b)]]
  739. lin_sys = DomainMatrix(lin_sys_elements, (1, a*b), K).transpose()
  740. homo_sys = DomainMatrix.zeros((a*b, 1), K)
  741. sol = _find_nonzero_solution(lin_sys, homo_sys)
  742. # until a non trivial solution is found
  743. while sol.is_zero_matrix:
  744. # updating the coefficients Dx^i(f).Dx^j(g) for next degree
  745. for i in range(a - 1, -1, -1):
  746. for j in range(b - 1, -1, -1):
  747. coeff_mul[i][j + 1] += coeff_mul[i][j]
  748. coeff_mul[i + 1][j] += coeff_mul[i][j]
  749. if isinstance(coeff_mul[i][j], K.dtype):
  750. coeff_mul[i][j] = DMFdiff(coeff_mul[i][j], K)
  751. else:
  752. coeff_mul[i][j] = coeff_mul[i][j].diff(self.x)
  753. # reduce the terms to lower power using annihilators of f, g
  754. for i in range(a + 1):
  755. if coeff_mul[i][b].is_zero:
  756. continue
  757. for j in range(b):
  758. coeff_mul[i][j] += other_red[j] * coeff_mul[i][b]
  759. coeff_mul[i][b] = K.zero
  760. # not d2 + 1, as that is already covered in previous loop
  761. for j in range(b):
  762. if coeff_mul[a][j] == 0:
  763. continue
  764. for i in range(a):
  765. coeff_mul[i][j] += self_red[i] * coeff_mul[a][j]
  766. coeff_mul[a][j] = K.zero
  767. lin_sys_elements.append([coeff_mul[i][j] for i in range(a) for j in range(b)])
  768. lin_sys = DomainMatrix(lin_sys_elements, (len(lin_sys_elements), a*b), K).transpose()
  769. sol = _find_nonzero_solution(lin_sys, homo_sys)
  770. sol_ann = _normalize(sol.flat(), self.annihilator.parent, negative=False)
  771. if not (self._have_init_cond() and other._have_init_cond()):
  772. return HolonomicFunction(sol_ann, self.x)
  773. if self.is_singularics() == False and other.is_singularics() == False:
  774. # if both the conditions are at same point
  775. if self.x0 == other.x0:
  776. # try to find more initial conditions
  777. y0_self = _extend_y0(self, sol_ann.order)
  778. y0_other = _extend_y0(other, sol_ann.order)
  779. # h(x0) = f(x0) * g(x0)
  780. y0 = [y0_self[0] * y0_other[0]]
  781. # coefficient of Dx^j(f)*Dx^i(g) in Dx^i(fg)
  782. for i in range(1, min(len(y0_self), len(y0_other))):
  783. coeff = [[0 for i in range(i + 1)] for j in range(i + 1)]
  784. for j in range(i + 1):
  785. for k in range(i + 1):
  786. if j + k == i:
  787. coeff[j][k] = binomial(i, j)
  788. sol = 0
  789. for j in range(i + 1):
  790. for k in range(i + 1):
  791. sol += coeff[j][k]* y0_self[j] * y0_other[k]
  792. y0.append(sol)
  793. return HolonomicFunction(sol_ann, self.x, self.x0, y0)
  794. # if the points are different, consider one
  795. selfat0 = self.annihilator.is_singular(0)
  796. otherat0 = other.annihilator.is_singular(0)
  797. if self.x0 == 0 and not selfat0 and not otherat0:
  798. return self * other.change_ics(0)
  799. if other.x0 == 0 and not selfat0 and not otherat0:
  800. return self.change_ics(0) * other
  801. selfatx0 = self.annihilator.is_singular(self.x0)
  802. otheratx0 = other.annihilator.is_singular(self.x0)
  803. if not selfatx0 and not otheratx0:
  804. return self * other.change_ics(self.x0)
  805. return self.change_ics(other.x0) * other
  806. if self.x0 != other.x0:
  807. return HolonomicFunction(sol_ann, self.x)
  808. # if the functions have singular_ics
  809. y1 = None
  810. y2 = None
  811. if self.is_singularics() == False and other.is_singularics() == True:
  812. _y0 = [j / factorial(i) for i, j in enumerate(self.y0)]
  813. y1 = {S.Zero: _y0}
  814. y2 = other.y0
  815. elif self.is_singularics() == True and other.is_singularics() == False:
  816. _y0 = [j / factorial(i) for i, j in enumerate(other.y0)]
  817. y1 = self.y0
  818. y2 = {S.Zero: _y0}
  819. elif self.is_singularics() == True and other.is_singularics() == True:
  820. y1 = self.y0
  821. y2 = other.y0
  822. y0 = {}
  823. # multiply every possible pair of the series terms
  824. for i in y1:
  825. for j in y2:
  826. k = min(len(y1[i]), len(y2[j]))
  827. c = [sum((y1[i][b] * y2[j][a - b] for b in range(a + 1)),
  828. start=S.Zero) for a in range(k)]
  829. if not i + j in y0:
  830. y0[i + j] = c
  831. else:
  832. y0[i + j] = [a + b for a, b in zip(c, y0[i + j])]
  833. return HolonomicFunction(sol_ann, self.x, self.x0, y0)
  834. __rmul__ = __mul__
  835. def __sub__(self, other):
  836. return self + other * -1
  837. def __rsub__(self, other):
  838. return self * -1 + other
  839. def __neg__(self):
  840. return -1 * self
  841. def __truediv__(self, other):
  842. return self * (S.One / other)
  843. def __pow__(self, n):
  844. if self.annihilator.order <= 1:
  845. ann = self.annihilator
  846. parent = ann.parent
  847. if self.y0 is None:
  848. y0 = None
  849. else:
  850. y0 = [list(self.y0)[0] ** n]
  851. p0 = ann.listofpoly[0]
  852. p1 = ann.listofpoly[1]
  853. p0 = (Poly.new(p0, self.x) * n).rep
  854. sol = [parent.base.to_sympy(i) for i in [p0, p1]]
  855. dd = DifferentialOperator(sol, parent)
  856. return HolonomicFunction(dd, self.x, self.x0, y0)
  857. if n < 0:
  858. raise NotHolonomicError("Negative Power on a Holonomic Function")
  859. Dx = self.annihilator.parent.derivative_operator
  860. result = HolonomicFunction(Dx, self.x, S.Zero, [S.One])
  861. if n == 0:
  862. return result
  863. x = self
  864. while True:
  865. if n % 2:
  866. result *= x
  867. n >>= 1
  868. if not n:
  869. break
  870. x *= x
  871. return result
  872. def degree(self):
  873. """
  874. Returns the highest power of `x` in the annihilator.
  875. """
  876. return max(i.degree() for i in self.annihilator.listofpoly)
  877. def composition(self, expr, *args, **kwargs):
  878. """
  879. Returns function after composition of a holonomic
  880. function with an algebraic function. The method cannot compute
  881. initial conditions for the result by itself, so they can be also be
  882. provided.
  883. Examples
  884. ========
  885. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  886. >>> from sympy import QQ
  887. >>> from sympy import symbols
  888. >>> x = symbols('x')
  889. >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx')
  890. >>> HolonomicFunction(Dx - 1, x).composition(x**2, 0, [1]) # e^(x**2)
  891. HolonomicFunction((-2*x) + (1)*Dx, x, 0, [1])
  892. >>> HolonomicFunction(Dx**2 + 1, x).composition(x**2 - 1, 1, [1, 0])
  893. HolonomicFunction((4*x**3) + (-1)*Dx + (x)*Dx**2, x, 1, [1, 0])
  894. See Also
  895. ========
  896. from_hyper
  897. """
  898. R = self.annihilator.parent
  899. a = self.annihilator.order
  900. diff = expr.diff(self.x)
  901. listofpoly = self.annihilator.listofpoly
  902. for i, j in enumerate(listofpoly):
  903. if isinstance(j, self.annihilator.parent.base.dtype):
  904. listofpoly[i] = self.annihilator.parent.base.to_sympy(j)
  905. r = listofpoly[a].subs({self.x:expr})
  906. subs = [-listofpoly[i].subs({self.x:expr}) / r for i in range (a)]
  907. coeffs = [S.Zero for i in range(a)] # coeffs[i] == coeff of (D^i f)(a) in D^k (f(a))
  908. coeffs[0] = S.One
  909. system = [coeffs]
  910. homogeneous = Matrix([[S.Zero for i in range(a)]]).transpose()
  911. while True:
  912. coeffs_next = [p.diff(self.x) for p in coeffs]
  913. for i in range(a - 1):
  914. coeffs_next[i + 1] += (coeffs[i] * diff)
  915. for i in range(a):
  916. coeffs_next[i] += (coeffs[-1] * subs[i] * diff)
  917. coeffs = coeffs_next
  918. # check for linear relations
  919. system.append(coeffs)
  920. sol, taus = (Matrix(system).transpose()
  921. ).gauss_jordan_solve(homogeneous)
  922. if sol.is_zero_matrix is not True:
  923. break
  924. tau = list(taus)[0]
  925. sol = sol.subs(tau, 1)
  926. sol = _normalize(sol[0:], R, negative=False)
  927. # if initial conditions are given for the resulting function
  928. if args:
  929. return HolonomicFunction(sol, self.x, args[0], args[1])
  930. return HolonomicFunction(sol, self.x)
  931. def to_sequence(self, lb=True):
  932. r"""
  933. Finds recurrence relation for the coefficients in the series expansion
  934. of the function about :math:`x_0`, where :math:`x_0` is the point at
  935. which the initial condition is stored.
  936. Explanation
  937. ===========
  938. If the point :math:`x_0` is ordinary, solution of the form :math:`[(R, n_0)]`
  939. is returned. Where :math:`R` is the recurrence relation and :math:`n_0` is the
  940. smallest ``n`` for which the recurrence holds true.
  941. If the point :math:`x_0` is regular singular, a list of solutions in
  942. the format :math:`(R, p, n_0)` is returned, i.e. `[(R, p, n_0), ... ]`.
  943. Each tuple in this vector represents a recurrence relation :math:`R`
  944. associated with a root of the indicial equation ``p``. Conditions of
  945. a different format can also be provided in this case, see the
  946. docstring of HolonomicFunction class.
  947. If it's not possible to numerically compute a initial condition,
  948. it is returned as a symbol :math:`C_j`, denoting the coefficient of
  949. :math:`(x - x_0)^j` in the power series about :math:`x_0`.
  950. Examples
  951. ========
  952. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  953. >>> from sympy import QQ
  954. >>> from sympy import symbols, S
  955. >>> x = symbols('x')
  956. >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx')
  957. >>> HolonomicFunction(Dx - 1, x, 0, [1]).to_sequence()
  958. [(HolonomicSequence((-1) + (n + 1)Sn, n), u(0) = 1, 0)]
  959. >>> HolonomicFunction((1 + x)*Dx**2 + Dx, x, 0, [0, 1]).to_sequence()
  960. [(HolonomicSequence((n**2) + (n**2 + n)Sn, n), u(0) = 0, u(1) = 1, u(2) = -1/2, 2)]
  961. >>> HolonomicFunction(-S(1)/2 + x*Dx, x, 0, {S(1)/2: [1]}).to_sequence()
  962. [(HolonomicSequence((n), n), u(0) = 1, 1/2, 1)]
  963. See Also
  964. ========
  965. HolonomicFunction.series
  966. References
  967. ==========
  968. .. [1] https://hal.inria.fr/inria-00070025/document
  969. .. [2] https://www3.risc.jku.at/publications/download/risc_2244/DIPLFORM.pdf
  970. """
  971. if self.x0 != 0:
  972. return self.shift_x(self.x0).to_sequence()
  973. # check whether a power series exists if the point is singular
  974. if self.annihilator.is_singular(self.x0):
  975. return self._frobenius(lb=lb)
  976. dict1 = {}
  977. n = Symbol('n', integer=True)
  978. dom = self.annihilator.parent.base.dom
  979. R, _ = RecurrenceOperators(dom.old_poly_ring(n), 'Sn')
  980. # substituting each term of the form `x^k Dx^j` in the
  981. # annihilator, according to the formula below:
  982. # x^k Dx^j = Sum(rf(n + 1 - k, j) * a(n + j - k) * x^n, (n, k, oo))
  983. # for explanation see [2].
  984. for i, j in enumerate(self.annihilator.listofpoly):
  985. listofdmp = j.all_coeffs()
  986. degree = len(listofdmp) - 1
  987. for k in range(degree + 1):
  988. coeff = listofdmp[degree - k]
  989. if coeff == 0:
  990. continue
  991. if (i - k, k) in dict1:
  992. dict1[(i - k, k)] += (dom.to_sympy(coeff) * rf(n - k + 1, i))
  993. else:
  994. dict1[(i - k, k)] = (dom.to_sympy(coeff) * rf(n - k + 1, i))
  995. sol = []
  996. keylist = [i[0] for i in dict1]
  997. lower = min(keylist)
  998. upper = max(keylist)
  999. degree = self.degree()
  1000. # the recurrence relation holds for all values of
  1001. # n greater than smallest_n, i.e. n >= smallest_n
  1002. smallest_n = lower + degree
  1003. dummys = {}
  1004. eqs = []
  1005. unknowns = []
  1006. # an appropriate shift of the recurrence
  1007. for j in range(lower, upper + 1):
  1008. if j in keylist:
  1009. temp = sum((v.subs(n, n - lower)
  1010. for k, v in dict1.items() if k[0] == j),
  1011. start=S.Zero)
  1012. sol.append(temp)
  1013. else:
  1014. sol.append(S.Zero)
  1015. # the recurrence relation
  1016. sol = RecurrenceOperator(sol, R)
  1017. # computing the initial conditions for recurrence
  1018. order = sol.order
  1019. all_roots = roots(R.base.to_sympy(sol.listofpoly[-1]), n, filter='Z')
  1020. all_roots = all_roots.keys()
  1021. if all_roots:
  1022. max_root = max(all_roots) + 1
  1023. smallest_n = max(max_root, smallest_n)
  1024. order += smallest_n
  1025. y0 = _extend_y0(self, order)
  1026. # u(n) = y^n(0)/factorial(n)
  1027. u0 = [j / factorial(i) for i, j in enumerate(y0)]
  1028. # if sufficient conditions can't be computed then
  1029. # try to use the series method i.e.
  1030. # equate the coefficients of x^k in the equation formed by
  1031. # substituting the series in differential equation, to zero.
  1032. if len(u0) < order:
  1033. for i in range(degree):
  1034. eq = S.Zero
  1035. for j in dict1:
  1036. if i + j[0] < 0:
  1037. dummys[i + j[0]] = S.Zero
  1038. elif i + j[0] < len(u0):
  1039. dummys[i + j[0]] = u0[i + j[0]]
  1040. elif not i + j[0] in dummys:
  1041. dummys[i + j[0]] = Symbol('C_%s' %(i + j[0]))
  1042. unknowns.append(dummys[i + j[0]])
  1043. if j[1] <= i:
  1044. eq += dict1[j].subs(n, i) * dummys[i + j[0]]
  1045. eqs.append(eq)
  1046. # solve the system of equations formed
  1047. soleqs = solve(eqs, *unknowns)
  1048. if isinstance(soleqs, dict):
  1049. for i in range(len(u0), order):
  1050. if i not in dummys:
  1051. dummys[i] = Symbol('C_%s' %i)
  1052. if dummys[i] in soleqs:
  1053. u0.append(soleqs[dummys[i]])
  1054. else:
  1055. u0.append(dummys[i])
  1056. if lb:
  1057. return [(HolonomicSequence(sol, u0), smallest_n)]
  1058. return [HolonomicSequence(sol, u0)]
  1059. for i in range(len(u0), order):
  1060. if i not in dummys:
  1061. dummys[i] = Symbol('C_%s' %i)
  1062. s = False
  1063. for j in soleqs:
  1064. if dummys[i] in j:
  1065. u0.append(j[dummys[i]])
  1066. s = True
  1067. if not s:
  1068. u0.append(dummys[i])
  1069. if lb:
  1070. return [(HolonomicSequence(sol, u0), smallest_n)]
  1071. return [HolonomicSequence(sol, u0)]
  1072. def _frobenius(self, lb=True):
  1073. # compute the roots of indicial equation
  1074. indicialroots = self._indicial()
  1075. reals = []
  1076. compl = []
  1077. for i in ordered(indicialroots.keys()):
  1078. if i.is_real:
  1079. reals.extend([i] * indicialroots[i])
  1080. else:
  1081. a, b = i.as_real_imag()
  1082. compl.extend([(i, a, b)] * indicialroots[i])
  1083. # sort the roots for a fixed ordering of solution
  1084. compl.sort(key=lambda x : x[1])
  1085. compl.sort(key=lambda x : x[2])
  1086. reals.sort()
  1087. # grouping the roots, roots differ by an integer are put in the same group.
  1088. grp = []
  1089. for i in reals:
  1090. if len(grp) == 0:
  1091. grp.append([i])
  1092. continue
  1093. for j in grp:
  1094. if int_valued(j[0] - i):
  1095. j.append(i)
  1096. break
  1097. else:
  1098. grp.append([i])
  1099. # True if none of the roots differ by an integer i.e.
  1100. # each element in group have only one member
  1101. independent = all(len(i) == 1 for i in grp)
  1102. allpos = all(i >= 0 for i in reals)
  1103. allint = all(int_valued(i) for i in reals)
  1104. # if initial conditions are provided
  1105. # then use them.
  1106. if self.is_singularics() == True:
  1107. rootstoconsider = []
  1108. for i in ordered(self.y0.keys()):
  1109. for j in ordered(indicialroots.keys()):
  1110. if equal_valued(j, i):
  1111. rootstoconsider.append(i)
  1112. elif allpos and allint:
  1113. rootstoconsider = [min(reals)]
  1114. elif independent:
  1115. rootstoconsider = [i[0] for i in grp] + [j[0] for j in compl]
  1116. elif not allint:
  1117. rootstoconsider = [i for i in reals if not int(i) == i]
  1118. elif not allpos:
  1119. if not self._have_init_cond() or S(self.y0[0]).is_finite == False:
  1120. rootstoconsider = [min(reals)]
  1121. else:
  1122. posroots = [i for i in reals if i >= 0]
  1123. rootstoconsider = [min(posroots)]
  1124. n = Symbol('n', integer=True)
  1125. dom = self.annihilator.parent.base.dom
  1126. R, _ = RecurrenceOperators(dom.old_poly_ring(n), 'Sn')
  1127. finalsol = []
  1128. char = ord('C')
  1129. for p in rootstoconsider:
  1130. dict1 = {}
  1131. for i, j in enumerate(self.annihilator.listofpoly):
  1132. listofdmp = j.all_coeffs()
  1133. degree = len(listofdmp) - 1
  1134. for k in range(degree + 1):
  1135. coeff = listofdmp[degree - k]
  1136. if coeff == 0:
  1137. continue
  1138. if (i - k, k - i) in dict1:
  1139. dict1[(i - k, k - i)] += (dom.to_sympy(coeff) * rf(n - k + 1 + p, i))
  1140. else:
  1141. dict1[(i - k, k - i)] = (dom.to_sympy(coeff) * rf(n - k + 1 + p, i))
  1142. sol = []
  1143. keylist = [i[0] for i in dict1]
  1144. lower = min(keylist)
  1145. upper = max(keylist)
  1146. degree = max(i[1] for i in dict1)
  1147. degree2 = min(i[1] for i in dict1)
  1148. smallest_n = lower + degree
  1149. dummys = {}
  1150. eqs = []
  1151. unknowns = []
  1152. for j in range(lower, upper + 1):
  1153. if j in keylist:
  1154. temp = sum((v.subs(n, n - lower)
  1155. for k, v in dict1.items() if k[0] == j),
  1156. start=S.Zero)
  1157. sol.append(temp)
  1158. else:
  1159. sol.append(S.Zero)
  1160. # the recurrence relation
  1161. sol = RecurrenceOperator(sol, R)
  1162. # computing the initial conditions for recurrence
  1163. order = sol.order
  1164. all_roots = roots(R.base.to_sympy(sol.listofpoly[-1]), n, filter='Z')
  1165. all_roots = all_roots.keys()
  1166. if all_roots:
  1167. max_root = max(all_roots) + 1
  1168. smallest_n = max(max_root, smallest_n)
  1169. order += smallest_n
  1170. u0 = []
  1171. if self.is_singularics() == True:
  1172. u0 = self.y0[p]
  1173. elif self.is_singularics() == False and p >= 0 and int(p) == p and len(rootstoconsider) == 1:
  1174. y0 = _extend_y0(self, order + int(p))
  1175. # u(n) = y^n(0)/factorial(n)
  1176. if len(y0) > int(p):
  1177. u0 = [y0[i] / factorial(i) for i in range(int(p), len(y0))]
  1178. if len(u0) < order:
  1179. for i in range(degree2, degree):
  1180. eq = S.Zero
  1181. for j in dict1:
  1182. if i + j[0] < 0:
  1183. dummys[i + j[0]] = S.Zero
  1184. elif i + j[0] < len(u0):
  1185. dummys[i + j[0]] = u0[i + j[0]]
  1186. elif not i + j[0] in dummys:
  1187. letter = chr(char) + '_%s' %(i + j[0])
  1188. dummys[i + j[0]] = Symbol(letter)
  1189. unknowns.append(dummys[i + j[0]])
  1190. if j[1] <= i:
  1191. eq += dict1[j].subs(n, i) * dummys[i + j[0]]
  1192. eqs.append(eq)
  1193. # solve the system of equations formed
  1194. soleqs = solve(eqs, *unknowns)
  1195. if isinstance(soleqs, dict):
  1196. for i in range(len(u0), order):
  1197. if i not in dummys:
  1198. letter = chr(char) + '_%s' %i
  1199. dummys[i] = Symbol(letter)
  1200. if dummys[i] in soleqs:
  1201. u0.append(soleqs[dummys[i]])
  1202. else:
  1203. u0.append(dummys[i])
  1204. if lb:
  1205. finalsol.append((HolonomicSequence(sol, u0), p, smallest_n))
  1206. continue
  1207. else:
  1208. finalsol.append((HolonomicSequence(sol, u0), p))
  1209. continue
  1210. for i in range(len(u0), order):
  1211. if i not in dummys:
  1212. letter = chr(char) + '_%s' %i
  1213. dummys[i] = Symbol(letter)
  1214. s = False
  1215. for j in soleqs:
  1216. if dummys[i] in j:
  1217. u0.append(j[dummys[i]])
  1218. s = True
  1219. if not s:
  1220. u0.append(dummys[i])
  1221. if lb:
  1222. finalsol.append((HolonomicSequence(sol, u0), p, smallest_n))
  1223. else:
  1224. finalsol.append((HolonomicSequence(sol, u0), p))
  1225. char += 1
  1226. return finalsol
  1227. def series(self, n=6, coefficient=False, order=True, _recur=None):
  1228. r"""
  1229. Finds the power series expansion of given holonomic function about :math:`x_0`.
  1230. Explanation
  1231. ===========
  1232. A list of series might be returned if :math:`x_0` is a regular point with
  1233. multiple roots of the indicial equation.
  1234. Examples
  1235. ========
  1236. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  1237. >>> from sympy import QQ
  1238. >>> from sympy import symbols
  1239. >>> x = symbols('x')
  1240. >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx')
  1241. >>> HolonomicFunction(Dx - 1, x, 0, [1]).series() # e^x
  1242. 1 + x + x**2/2 + x**3/6 + x**4/24 + x**5/120 + O(x**6)
  1243. >>> HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]).series(n=8) # sin(x)
  1244. x - x**3/6 + x**5/120 - x**7/5040 + O(x**8)
  1245. See Also
  1246. ========
  1247. HolonomicFunction.to_sequence
  1248. """
  1249. if _recur is None:
  1250. recurrence = self.to_sequence()
  1251. else:
  1252. recurrence = _recur
  1253. if isinstance(recurrence, tuple) and len(recurrence) == 2:
  1254. recurrence = recurrence[0]
  1255. constantpower = 0
  1256. elif isinstance(recurrence, tuple) and len(recurrence) == 3:
  1257. constantpower = recurrence[1]
  1258. recurrence = recurrence[0]
  1259. elif len(recurrence) == 1 and len(recurrence[0]) == 2:
  1260. recurrence = recurrence[0][0]
  1261. constantpower = 0
  1262. elif len(recurrence) == 1 and len(recurrence[0]) == 3:
  1263. constantpower = recurrence[0][1]
  1264. recurrence = recurrence[0][0]
  1265. else:
  1266. return [self.series(_recur=i) for i in recurrence]
  1267. n = n - int(constantpower)
  1268. l = len(recurrence.u0) - 1
  1269. k = recurrence.recurrence.order
  1270. x = self.x
  1271. x0 = self.x0
  1272. seq_dmp = recurrence.recurrence.listofpoly
  1273. R = recurrence.recurrence.parent.base
  1274. K = R.get_field()
  1275. seq = [K.new(j.to_list()) for j in seq_dmp]
  1276. sub = [-seq[i] / seq[k] for i in range(k)]
  1277. sol = list(recurrence.u0)
  1278. if l + 1 < n:
  1279. # use the initial conditions to find the next term
  1280. for i in range(l + 1 - k, n - k):
  1281. coeff = sum((DMFsubs(sub[j], i) * sol[i + j]
  1282. for j in range(k) if i + j >= 0), start=S.Zero)
  1283. sol.append(coeff)
  1284. if coefficient:
  1285. return sol
  1286. ser = sum((x**(i + constantpower) * j for i, j in enumerate(sol)),
  1287. start=S.Zero)
  1288. if order:
  1289. ser += Order(x**(n + int(constantpower)), x)
  1290. if x0 != 0:
  1291. return ser.subs(x, x - x0)
  1292. return ser
  1293. def _indicial(self):
  1294. """
  1295. Computes roots of the Indicial equation.
  1296. """
  1297. if self.x0 != 0:
  1298. return self.shift_x(self.x0)._indicial()
  1299. list_coeff = self.annihilator.listofpoly
  1300. R = self.annihilator.parent.base
  1301. x = self.x
  1302. s = R.zero
  1303. y = R.one
  1304. def _pole_degree(poly):
  1305. root_all = roots(R.to_sympy(poly), x, filter='Z')
  1306. if 0 in root_all.keys():
  1307. return root_all[0]
  1308. else:
  1309. return 0
  1310. degree = max(j.degree() for j in list_coeff)
  1311. inf = 10 * (max(1, degree) + max(1, self.annihilator.order))
  1312. deg = lambda q: inf if q.is_zero else _pole_degree(q)
  1313. b = min(deg(q) - j for j, q in enumerate(list_coeff))
  1314. for i, j in enumerate(list_coeff):
  1315. listofdmp = j.all_coeffs()
  1316. degree = len(listofdmp) - 1
  1317. if 0 <= i + b <= degree:
  1318. s = s + listofdmp[degree - i - b] * y
  1319. y *= R.from_sympy(x - i)
  1320. return roots(R.to_sympy(s), x)
  1321. def evalf(self, points, method='RK4', h=0.05, derivatives=False):
  1322. r"""
  1323. Finds numerical value of a holonomic function using numerical methods.
  1324. (RK4 by default). A set of points (real or complex) must be provided
  1325. which will be the path for the numerical integration.
  1326. Explanation
  1327. ===========
  1328. The path should be given as a list :math:`[x_1, x_2, \dots x_n]`. The numerical
  1329. values will be computed at each point in this order
  1330. :math:`x_1 \rightarrow x_2 \rightarrow x_3 \dots \rightarrow x_n`.
  1331. Returns values of the function at :math:`x_1, x_2, \dots x_n` in a list.
  1332. Examples
  1333. ========
  1334. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  1335. >>> from sympy import QQ
  1336. >>> from sympy import symbols
  1337. >>> x = symbols('x')
  1338. >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx')
  1339. A straight line on the real axis from (0 to 1)
  1340. >>> r = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
  1341. Runge-Kutta 4th order on e^x from 0.1 to 1.
  1342. Exact solution at 1 is 2.71828182845905
  1343. >>> HolonomicFunction(Dx - 1, x, 0, [1]).evalf(r)
  1344. [1.10517083333333, 1.22140257085069, 1.34985849706254, 1.49182424008069,
  1345. 1.64872063859684, 1.82211796209193, 2.01375162659678, 2.22553956329232,
  1346. 2.45960141378007, 2.71827974413517]
  1347. Euler's method for the same
  1348. >>> HolonomicFunction(Dx - 1, x, 0, [1]).evalf(r, method='Euler')
  1349. [1.1, 1.21, 1.331, 1.4641, 1.61051, 1.771561, 1.9487171, 2.14358881,
  1350. 2.357947691, 2.5937424601]
  1351. One can also observe that the value obtained using Runge-Kutta 4th order
  1352. is much more accurate than Euler's method.
  1353. """
  1354. from sympy.holonomic.numerical import _evalf
  1355. lp = False
  1356. # if a point `b` is given instead of a mesh
  1357. if not hasattr(points, "__iter__"):
  1358. lp = True
  1359. b = S(points)
  1360. if self.x0 == b:
  1361. return _evalf(self, [b], method=method, derivatives=derivatives)[-1]
  1362. if not b.is_Number:
  1363. raise NotImplementedError
  1364. a = self.x0
  1365. if a > b:
  1366. h = -h
  1367. n = int((b - a) / h)
  1368. points = [a + h]
  1369. for i in range(n - 1):
  1370. points.append(points[-1] + h)
  1371. for i in roots(self.annihilator.parent.base.to_sympy(self.annihilator.listofpoly[-1]), self.x):
  1372. if i == self.x0 or i in points:
  1373. raise SingularityError(self, i)
  1374. if lp:
  1375. return _evalf(self, points, method=method, derivatives=derivatives)[-1]
  1376. return _evalf(self, points, method=method, derivatives=derivatives)
  1377. def change_x(self, z):
  1378. """
  1379. Changes only the variable of Holonomic Function, for internal
  1380. purposes. For composition use HolonomicFunction.composition()
  1381. """
  1382. dom = self.annihilator.parent.base.dom
  1383. R = dom.old_poly_ring(z)
  1384. parent, _ = DifferentialOperators(R, 'Dx')
  1385. sol = [R(j.to_list()) for j in self.annihilator.listofpoly]
  1386. sol = DifferentialOperator(sol, parent)
  1387. return HolonomicFunction(sol, z, self.x0, self.y0)
  1388. def shift_x(self, a):
  1389. """
  1390. Substitute `x + a` for `x`.
  1391. """
  1392. x = self.x
  1393. listaftershift = self.annihilator.listofpoly
  1394. base = self.annihilator.parent.base
  1395. sol = [base.from_sympy(base.to_sympy(i).subs(x, x + a)) for i in listaftershift]
  1396. sol = DifferentialOperator(sol, self.annihilator.parent)
  1397. x0 = self.x0 - a
  1398. if not self._have_init_cond():
  1399. return HolonomicFunction(sol, x)
  1400. return HolonomicFunction(sol, x, x0, self.y0)
  1401. def to_hyper(self, as_list=False, _recur=None):
  1402. r"""
  1403. Returns a hypergeometric function (or linear combination of them)
  1404. representing the given holonomic function.
  1405. Explanation
  1406. ===========
  1407. Returns an answer of the form:
  1408. `a_1 \cdot x^{b_1} \cdot{hyper()} + a_2 \cdot x^{b_2} \cdot{hyper()} \dots`
  1409. This is very useful as one can now use ``hyperexpand`` to find the
  1410. symbolic expressions/functions.
  1411. Examples
  1412. ========
  1413. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  1414. >>> from sympy import ZZ
  1415. >>> from sympy import symbols
  1416. >>> x = symbols('x')
  1417. >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx')
  1418. >>> # sin(x)
  1419. >>> HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]).to_hyper()
  1420. x*hyper((), (3/2,), -x**2/4)
  1421. >>> # exp(x)
  1422. >>> HolonomicFunction(Dx - 1, x, 0, [1]).to_hyper()
  1423. hyper((), (), x)
  1424. See Also
  1425. ========
  1426. from_hyper, from_meijerg
  1427. """
  1428. if _recur is None:
  1429. recurrence = self.to_sequence()
  1430. else:
  1431. recurrence = _recur
  1432. if isinstance(recurrence, tuple) and len(recurrence) == 2:
  1433. smallest_n = recurrence[1]
  1434. recurrence = recurrence[0]
  1435. constantpower = 0
  1436. elif isinstance(recurrence, tuple) and len(recurrence) == 3:
  1437. smallest_n = recurrence[2]
  1438. constantpower = recurrence[1]
  1439. recurrence = recurrence[0]
  1440. elif len(recurrence) == 1 and len(recurrence[0]) == 2:
  1441. smallest_n = recurrence[0][1]
  1442. recurrence = recurrence[0][0]
  1443. constantpower = 0
  1444. elif len(recurrence) == 1 and len(recurrence[0]) == 3:
  1445. smallest_n = recurrence[0][2]
  1446. constantpower = recurrence[0][1]
  1447. recurrence = recurrence[0][0]
  1448. else:
  1449. sol = self.to_hyper(as_list=as_list, _recur=recurrence[0])
  1450. for i in recurrence[1:]:
  1451. sol += self.to_hyper(as_list=as_list, _recur=i)
  1452. return sol
  1453. u0 = recurrence.u0
  1454. r = recurrence.recurrence
  1455. x = self.x
  1456. x0 = self.x0
  1457. # order of the recurrence relation
  1458. m = r.order
  1459. # when no recurrence exists, and the power series have finite terms
  1460. if m == 0:
  1461. nonzeroterms = roots(r.parent.base.to_sympy(r.listofpoly[0]), recurrence.n, filter='R')
  1462. sol = S.Zero
  1463. for j, i in enumerate(nonzeroterms):
  1464. if i < 0 or not int_valued(i):
  1465. continue
  1466. i = int(i)
  1467. if i < len(u0):
  1468. if isinstance(u0[i], (PolyElement, FracElement)):
  1469. u0[i] = u0[i].as_expr()
  1470. sol += u0[i] * x**i
  1471. else:
  1472. sol += Symbol('C_%s' %j) * x**i
  1473. if isinstance(sol, (PolyElement, FracElement)):
  1474. sol = sol.as_expr() * x**constantpower
  1475. else:
  1476. sol = sol * x**constantpower
  1477. if as_list:
  1478. if x0 != 0:
  1479. return [(sol.subs(x, x - x0), )]
  1480. return [(sol, )]
  1481. if x0 != 0:
  1482. return sol.subs(x, x - x0)
  1483. return sol
  1484. if smallest_n + m > len(u0):
  1485. raise NotImplementedError("Can't compute sufficient Initial Conditions")
  1486. # check if the recurrence represents a hypergeometric series
  1487. if any(i != r.parent.base.zero for i in r.listofpoly[1:-1]):
  1488. raise NotHyperSeriesError(self, self.x0)
  1489. a = r.listofpoly[0]
  1490. b = r.listofpoly[-1]
  1491. # the constant multiple of argument of hypergeometric function
  1492. if isinstance(a.LC(), (PolyElement, FracElement)):
  1493. c = - (S(a.LC().as_expr()) * m**(a.degree())) / (S(b.LC().as_expr()) * m**(b.degree()))
  1494. else:
  1495. c = - (S(a.LC()) * m**(a.degree())) / (S(b.LC()) * m**(b.degree()))
  1496. sol = 0
  1497. arg1 = roots(r.parent.base.to_sympy(a), recurrence.n)
  1498. arg2 = roots(r.parent.base.to_sympy(b), recurrence.n)
  1499. # iterate through the initial conditions to find
  1500. # the hypergeometric representation of the given
  1501. # function.
  1502. # The answer will be a linear combination
  1503. # of different hypergeometric series which satisfies
  1504. # the recurrence.
  1505. if as_list:
  1506. listofsol = []
  1507. for i in range(smallest_n + m):
  1508. # if the recurrence relation doesn't hold for `n = i`,
  1509. # then a Hypergeometric representation doesn't exist.
  1510. # add the algebraic term a * x**i to the solution,
  1511. # where a is u0[i]
  1512. if i < smallest_n:
  1513. if as_list:
  1514. listofsol.append(((S(u0[i]) * x**(i+constantpower)).subs(x, x-x0), ))
  1515. else:
  1516. sol += S(u0[i]) * x**i
  1517. continue
  1518. # if the coefficient u0[i] is zero, then the
  1519. # independent hypergeomtric series starting with
  1520. # x**i is not a part of the answer.
  1521. if S(u0[i]) == 0:
  1522. continue
  1523. ap = []
  1524. bq = []
  1525. # substitute m * n + i for n
  1526. for k in ordered(arg1.keys()):
  1527. ap.extend([nsimplify((i - k) / m)] * arg1[k])
  1528. for k in ordered(arg2.keys()):
  1529. bq.extend([nsimplify((i - k) / m)] * arg2[k])
  1530. # convention of (k + 1) in the denominator
  1531. if 1 in bq:
  1532. bq.remove(1)
  1533. else:
  1534. ap.append(1)
  1535. if as_list:
  1536. listofsol.append(((S(u0[i])*x**(i+constantpower)).subs(x, x-x0), (hyper(ap, bq, c*x**m)).subs(x, x-x0)))
  1537. else:
  1538. sol += S(u0[i]) * hyper(ap, bq, c * x**m) * x**i
  1539. if as_list:
  1540. return listofsol
  1541. sol = sol * x**constantpower
  1542. if x0 != 0:
  1543. return sol.subs(x, x - x0)
  1544. return sol
  1545. def to_expr(self):
  1546. """
  1547. Converts a Holonomic Function back to elementary functions.
  1548. Examples
  1549. ========
  1550. >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators
  1551. >>> from sympy import ZZ
  1552. >>> from sympy import symbols, S
  1553. >>> x = symbols('x')
  1554. >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx')
  1555. >>> HolonomicFunction(x**2*Dx**2 + x*Dx + (x**2 - 1), x, 0, [0, S(1)/2]).to_expr()
  1556. besselj(1, x)
  1557. >>> HolonomicFunction((1 + x)*Dx**3 + Dx**2, x, 0, [1, 1, 1]).to_expr()
  1558. x*log(x + 1) + log(x + 1) + 1
  1559. """
  1560. return hyperexpand(self.to_hyper()).simplify()
  1561. def change_ics(self, b, lenics=None):
  1562. """
  1563. Changes the point `x0` to ``b`` for initial conditions.
  1564. Examples
  1565. ========
  1566. >>> from sympy.holonomic import expr_to_holonomic
  1567. >>> from sympy import symbols, sin, exp
  1568. >>> x = symbols('x')
  1569. >>> expr_to_holonomic(sin(x)).change_ics(1)
  1570. HolonomicFunction((1) + (1)*Dx**2, x, 1, [sin(1), cos(1)])
  1571. >>> expr_to_holonomic(exp(x)).change_ics(2)
  1572. HolonomicFunction((-1) + (1)*Dx, x, 2, [exp(2)])
  1573. """
  1574. symbolic = True
  1575. if lenics is None and len(self.y0) > self.annihilator.order:
  1576. lenics = len(self.y0)
  1577. dom = self.annihilator.parent.base.domain
  1578. try:
  1579. sol = expr_to_holonomic(self.to_expr(), x=self.x, x0=b, lenics=lenics, domain=dom)
  1580. except (NotPowerSeriesError, NotHyperSeriesError):
  1581. symbolic = False
  1582. if symbolic and sol.x0 == b:
  1583. return sol
  1584. y0 = self.evalf(b, derivatives=True)
  1585. return HolonomicFunction(self.annihilator, self.x, b, y0)
  1586. def to_meijerg(self):
  1587. """
  1588. Returns a linear combination of Meijer G-functions.
  1589. Examples
  1590. ========
  1591. >>> from sympy.holonomic import expr_to_holonomic
  1592. >>> from sympy import sin, cos, hyperexpand, log, symbols
  1593. >>> x = symbols('x')
  1594. >>> hyperexpand(expr_to_holonomic(cos(x) + sin(x)).to_meijerg())
  1595. sin(x) + cos(x)
  1596. >>> hyperexpand(expr_to_holonomic(log(x)).to_meijerg()).simplify()
  1597. log(x)
  1598. See Also
  1599. ========
  1600. to_hyper
  1601. """
  1602. # convert to hypergeometric first
  1603. rep = self.to_hyper(as_list=True)
  1604. sol = S.Zero
  1605. for i in rep:
  1606. if len(i) == 1:
  1607. sol += i[0]
  1608. elif len(i) == 2:
  1609. sol += i[0] * _hyper_to_meijerg(i[1])
  1610. return sol
  1611. def from_hyper(func, x0=0, evalf=False):
  1612. r"""
  1613. Converts a hypergeometric function to holonomic.
  1614. ``func`` is the Hypergeometric Function and ``x0`` is the point at
  1615. which initial conditions are required.
  1616. Examples
  1617. ========
  1618. >>> from sympy.holonomic.holonomic import from_hyper
  1619. >>> from sympy import symbols, hyper, S
  1620. >>> x = symbols('x')
  1621. >>> from_hyper(hyper([], [S(3)/2], x**2/4))
  1622. HolonomicFunction((-x) + (2)*Dx + (x)*Dx**2, x, 1, [sinh(1), -sinh(1) + cosh(1)])
  1623. """
  1624. a = func.ap
  1625. b = func.bq
  1626. z = func.args[2]
  1627. x = z.atoms(Symbol).pop()
  1628. R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx')
  1629. # generalized hypergeometric differential equation
  1630. xDx = x*Dx
  1631. r1 = 1
  1632. for ai in a: # XXX gives sympify error if Mul is used with list of all factors
  1633. r1 *= xDx + ai
  1634. xDx_1 = xDx - 1
  1635. # r2 = Mul(*([Dx] + [xDx_1 + bi for bi in b])) # XXX gives sympify error
  1636. r2 = Dx
  1637. for bi in b:
  1638. r2 *= xDx_1 + bi
  1639. sol = r1 - r2
  1640. simp = hyperexpand(func)
  1641. if simp in (Infinity, NegativeInfinity):
  1642. return HolonomicFunction(sol, x).composition(z)
  1643. # if the function is known symbolically
  1644. if not isinstance(simp, hyper):
  1645. y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False)
  1646. while not y0:
  1647. # if values don't exist at 0, then try to find initial
  1648. # conditions at 1. If it doesn't exist at 1 too then
  1649. # try 2 and so on.
  1650. x0 += 1
  1651. y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False)
  1652. return HolonomicFunction(sol, x).composition(z, x0, y0)
  1653. if isinstance(simp, hyper):
  1654. x0 = 1
  1655. # use evalf if the function can't be simplified
  1656. y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False)
  1657. while not y0:
  1658. x0 += 1
  1659. y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False)
  1660. return HolonomicFunction(sol, x).composition(z, x0, y0)
  1661. return HolonomicFunction(sol, x).composition(z)
  1662. def from_meijerg(func, x0=0, evalf=False, initcond=True, domain=QQ):
  1663. """
  1664. Converts a Meijer G-function to Holonomic.
  1665. ``func`` is the G-Function and ``x0`` is the point at
  1666. which initial conditions are required.
  1667. Examples
  1668. ========
  1669. >>> from sympy.holonomic.holonomic import from_meijerg
  1670. >>> from sympy import symbols, meijerg, S
  1671. >>> x = symbols('x')
  1672. >>> from_meijerg(meijerg(([], []), ([S(1)/2], [0]), x**2/4))
  1673. HolonomicFunction((1) + (1)*Dx**2, x, 0, [0, 1/sqrt(pi)])
  1674. """
  1675. a = func.ap
  1676. b = func.bq
  1677. n = len(func.an)
  1678. m = len(func.bm)
  1679. p = len(a)
  1680. z = func.args[2]
  1681. x = z.atoms(Symbol).pop()
  1682. R, Dx = DifferentialOperators(domain.old_poly_ring(x), 'Dx')
  1683. # compute the differential equation satisfied by the
  1684. # Meijer G-function.
  1685. xDx = x*Dx
  1686. xDx1 = xDx + 1
  1687. r1 = x*(-1)**(m + n - p)
  1688. for ai in a: # XXX gives sympify error if args given in list
  1689. r1 *= xDx1 - ai
  1690. # r2 = Mul(*[xDx - bi for bi in b]) # gives sympify error
  1691. r2 = 1
  1692. for bi in b:
  1693. r2 *= xDx - bi
  1694. sol = r1 - r2
  1695. if not initcond:
  1696. return HolonomicFunction(sol, x).composition(z)
  1697. simp = hyperexpand(func)
  1698. if simp in (Infinity, NegativeInfinity):
  1699. return HolonomicFunction(sol, x).composition(z)
  1700. # computing initial conditions
  1701. if not isinstance(simp, meijerg):
  1702. y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False)
  1703. while not y0:
  1704. x0 += 1
  1705. y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False)
  1706. return HolonomicFunction(sol, x).composition(z, x0, y0)
  1707. if isinstance(simp, meijerg):
  1708. x0 = 1
  1709. y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False)
  1710. while not y0:
  1711. x0 += 1
  1712. y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False)
  1713. return HolonomicFunction(sol, x).composition(z, x0, y0)
  1714. return HolonomicFunction(sol, x).composition(z)
  1715. x_1 = Dummy('x_1')
  1716. _lookup_table = None
  1717. domain_for_table = None
  1718. from sympy.integrals.meijerint import _mytype
  1719. def expr_to_holonomic(func, x=None, x0=0, y0=None, lenics=None, domain=None, initcond=True):
  1720. """
  1721. Converts a function or an expression to a holonomic function.
  1722. Parameters
  1723. ==========
  1724. func:
  1725. The expression to be converted.
  1726. x:
  1727. variable for the function.
  1728. x0:
  1729. point at which initial condition must be computed.
  1730. y0:
  1731. One can optionally provide initial condition if the method
  1732. is not able to do it automatically.
  1733. lenics:
  1734. Number of terms in the initial condition. By default it is
  1735. equal to the order of the annihilator.
  1736. domain:
  1737. Ground domain for the polynomials in ``x`` appearing as coefficients
  1738. in the annihilator.
  1739. initcond:
  1740. Set it false if you do not want the initial conditions to be computed.
  1741. Examples
  1742. ========
  1743. >>> from sympy.holonomic.holonomic import expr_to_holonomic
  1744. >>> from sympy import sin, exp, symbols
  1745. >>> x = symbols('x')
  1746. >>> expr_to_holonomic(sin(x))
  1747. HolonomicFunction((1) + (1)*Dx**2, x, 0, [0, 1])
  1748. >>> expr_to_holonomic(exp(x))
  1749. HolonomicFunction((-1) + (1)*Dx, x, 0, [1])
  1750. See Also
  1751. ========
  1752. sympy.integrals.meijerint._rewrite1, _convert_poly_rat_alg, _create_table
  1753. """
  1754. func = sympify(func)
  1755. syms = func.free_symbols
  1756. if not x:
  1757. if len(syms) == 1:
  1758. x= syms.pop()
  1759. else:
  1760. raise ValueError("Specify the variable for the function")
  1761. elif x in syms:
  1762. syms.remove(x)
  1763. extra_syms = list(syms)
  1764. if domain is None:
  1765. if func.has(Float):
  1766. domain = RR
  1767. else:
  1768. domain = QQ
  1769. if len(extra_syms) != 0:
  1770. domain = domain[extra_syms].get_field()
  1771. # try to convert if the function is polynomial or rational
  1772. solpoly = _convert_poly_rat_alg(func, x, x0=x0, y0=y0, lenics=lenics, domain=domain, initcond=initcond)
  1773. if solpoly:
  1774. return solpoly
  1775. # create the lookup table
  1776. global _lookup_table, domain_for_table
  1777. if not _lookup_table:
  1778. domain_for_table = domain
  1779. _lookup_table = {}
  1780. _create_table(_lookup_table, domain=domain)
  1781. elif domain != domain_for_table:
  1782. domain_for_table = domain
  1783. _lookup_table = {}
  1784. _create_table(_lookup_table, domain=domain)
  1785. # use the table directly to convert to Holonomic
  1786. if func.is_Function:
  1787. f = func.subs(x, x_1)
  1788. t = _mytype(f, x_1)
  1789. if t in _lookup_table:
  1790. l = _lookup_table[t]
  1791. sol = l[0][1].change_x(x)
  1792. else:
  1793. sol = _convert_meijerint(func, x, initcond=False, domain=domain)
  1794. if not sol:
  1795. raise NotImplementedError
  1796. if y0:
  1797. sol.y0 = y0
  1798. if y0 or not initcond:
  1799. sol.x0 = x0
  1800. return sol
  1801. if not lenics:
  1802. lenics = sol.annihilator.order
  1803. _y0 = _find_conditions(func, x, x0, lenics)
  1804. while not _y0:
  1805. x0 += 1
  1806. _y0 = _find_conditions(func, x, x0, lenics)
  1807. return HolonomicFunction(sol.annihilator, x, x0, _y0)
  1808. if y0 or not initcond:
  1809. sol = sol.composition(func.args[0])
  1810. if y0:
  1811. sol.y0 = y0
  1812. sol.x0 = x0
  1813. return sol
  1814. if not lenics:
  1815. lenics = sol.annihilator.order
  1816. _y0 = _find_conditions(func, x, x0, lenics)
  1817. while not _y0:
  1818. x0 += 1
  1819. _y0 = _find_conditions(func, x, x0, lenics)
  1820. return sol.composition(func.args[0], x0, _y0)
  1821. # iterate through the expression recursively
  1822. args = func.args
  1823. f = func.func
  1824. sol = expr_to_holonomic(args[0], x=x, initcond=False, domain=domain)
  1825. if f is Add:
  1826. for i in range(1, len(args)):
  1827. sol += expr_to_holonomic(args[i], x=x, initcond=False, domain=domain)
  1828. elif f is Mul:
  1829. for i in range(1, len(args)):
  1830. sol *= expr_to_holonomic(args[i], x=x, initcond=False, domain=domain)
  1831. elif f is Pow:
  1832. sol = sol**args[1]
  1833. sol.x0 = x0
  1834. if not sol:
  1835. raise NotImplementedError
  1836. if y0:
  1837. sol.y0 = y0
  1838. if y0 or not initcond:
  1839. return sol
  1840. if sol.y0:
  1841. return sol
  1842. if not lenics:
  1843. lenics = sol.annihilator.order
  1844. if sol.annihilator.is_singular(x0):
  1845. r = sol._indicial()
  1846. l = list(r)
  1847. if len(r) == 1 and r[l[0]] == S.One:
  1848. r = l[0]
  1849. g = func / (x - x0)**r
  1850. singular_ics = _find_conditions(g, x, x0, lenics)
  1851. singular_ics = [j / factorial(i) for i, j in enumerate(singular_ics)]
  1852. y0 = {r:singular_ics}
  1853. return HolonomicFunction(sol.annihilator, x, x0, y0)
  1854. _y0 = _find_conditions(func, x, x0, lenics)
  1855. while not _y0:
  1856. x0 += 1
  1857. _y0 = _find_conditions(func, x, x0, lenics)
  1858. return HolonomicFunction(sol.annihilator, x, x0, _y0)
  1859. ## Some helper functions ##
  1860. def _normalize(list_of, parent, negative=True):
  1861. """
  1862. Normalize a given annihilator
  1863. """
  1864. num = []
  1865. denom = []
  1866. base = parent.base
  1867. K = base.get_field()
  1868. lcm_denom = base.from_sympy(S.One)
  1869. list_of_coeff = []
  1870. # convert polynomials to the elements of associated
  1871. # fraction field
  1872. for i, j in enumerate(list_of):
  1873. if isinstance(j, base.dtype):
  1874. list_of_coeff.append(K.new(j.to_list()))
  1875. elif not isinstance(j, K.dtype):
  1876. list_of_coeff.append(K.from_sympy(sympify(j)))
  1877. else:
  1878. list_of_coeff.append(j)
  1879. # corresponding numerators of the sequence of polynomials
  1880. num.append(list_of_coeff[i].numer())
  1881. # corresponding denominators
  1882. denom.append(list_of_coeff[i].denom())
  1883. # lcm of denominators in the coefficients
  1884. for i in denom:
  1885. lcm_denom = i.lcm(lcm_denom)
  1886. if negative:
  1887. lcm_denom = -lcm_denom
  1888. lcm_denom = K.new(lcm_denom.to_list())
  1889. # multiply the coefficients with lcm
  1890. for i, j in enumerate(list_of_coeff):
  1891. list_of_coeff[i] = j * lcm_denom
  1892. gcd_numer = base((list_of_coeff[-1].numer() / list_of_coeff[-1].denom()).to_list())
  1893. # gcd of numerators in the coefficients
  1894. for i in num:
  1895. gcd_numer = i.gcd(gcd_numer)
  1896. gcd_numer = K.new(gcd_numer.to_list())
  1897. # divide all the coefficients by the gcd
  1898. for i, j in enumerate(list_of_coeff):
  1899. frac_ans = j / gcd_numer
  1900. list_of_coeff[i] = base((frac_ans.numer() / frac_ans.denom()).to_list())
  1901. return DifferentialOperator(list_of_coeff, parent)
  1902. def _derivate_diff_eq(listofpoly, K):
  1903. """
  1904. Let a differential equation a0(x)y(x) + a1(x)y'(x) + ... = 0
  1905. where a0, a1,... are polynomials or rational functions. The function
  1906. returns b0, b1, b2... such that the differential equation
  1907. b0(x)y(x) + b1(x)y'(x) +... = 0 is formed after differentiating the
  1908. former equation.
  1909. """
  1910. sol = []
  1911. a = len(listofpoly) - 1
  1912. sol.append(DMFdiff(listofpoly[0], K))
  1913. for i, j in enumerate(listofpoly[1:]):
  1914. sol.append(DMFdiff(j, K) + listofpoly[i])
  1915. sol.append(listofpoly[a])
  1916. return sol
  1917. def _hyper_to_meijerg(func):
  1918. """
  1919. Converts a `hyper` to meijerg.
  1920. """
  1921. ap = func.ap
  1922. bq = func.bq
  1923. if any(i <= 0 and int(i) == i for i in ap):
  1924. return hyperexpand(func)
  1925. z = func.args[2]
  1926. # parameters of the `meijerg` function.
  1927. an = (1 - i for i in ap)
  1928. anp = ()
  1929. bm = (S.Zero, )
  1930. bmq = (1 - i for i in bq)
  1931. k = S.One
  1932. for i in bq:
  1933. k = k * gamma(i)
  1934. for i in ap:
  1935. k = k / gamma(i)
  1936. return k * meijerg(an, anp, bm, bmq, -z)
  1937. def _add_lists(list1, list2):
  1938. """Takes polynomial sequences of two annihilators a and b and returns
  1939. the list of polynomials of sum of a and b.
  1940. """
  1941. if len(list1) <= len(list2):
  1942. sol = [a + b for a, b in zip(list1, list2)] + list2[len(list1):]
  1943. else:
  1944. sol = [a + b for a, b in zip(list1, list2)] + list1[len(list2):]
  1945. return sol
  1946. def _extend_y0(Holonomic, n):
  1947. """
  1948. Tries to find more initial conditions by substituting the initial
  1949. value point in the differential equation.
  1950. """
  1951. if Holonomic.annihilator.is_singular(Holonomic.x0) or Holonomic.is_singularics() == True:
  1952. return Holonomic.y0
  1953. annihilator = Holonomic.annihilator
  1954. a = annihilator.order
  1955. listofpoly = []
  1956. y0 = Holonomic.y0
  1957. R = annihilator.parent.base
  1958. K = R.get_field()
  1959. for j in annihilator.listofpoly:
  1960. if isinstance(j, annihilator.parent.base.dtype):
  1961. listofpoly.append(K.new(j.to_list()))
  1962. if len(y0) < a or n <= len(y0):
  1963. return y0
  1964. list_red = [-listofpoly[i] / listofpoly[a]
  1965. for i in range(a)]
  1966. y1 = y0[:min(len(y0), a)]
  1967. for _ in range(n - a):
  1968. sol = 0
  1969. for a, b in zip(y1, list_red):
  1970. r = DMFsubs(b, Holonomic.x0)
  1971. if not getattr(r, 'is_finite', True):
  1972. return y0
  1973. if isinstance(r, (PolyElement, FracElement)):
  1974. r = r.as_expr()
  1975. sol += a * r
  1976. y1.append(sol)
  1977. list_red = _derivate_diff_eq(list_red, K)
  1978. return y0 + y1[len(y0):]
  1979. def DMFdiff(frac, K):
  1980. # differentiate a DMF object represented as p/q
  1981. if not isinstance(frac, DMF):
  1982. return frac.diff()
  1983. p = K.numer(frac)
  1984. q = K.denom(frac)
  1985. sol_num = - p * q.diff() + q * p.diff()
  1986. sol_denom = q**2
  1987. return K((sol_num.to_list(), sol_denom.to_list()))
  1988. def DMFsubs(frac, x0, mpm=False):
  1989. # substitute the point x0 in DMF object of the form p/q
  1990. if not isinstance(frac, DMF):
  1991. return frac
  1992. p = frac.num
  1993. q = frac.den
  1994. sol_p = S.Zero
  1995. sol_q = S.Zero
  1996. if mpm:
  1997. from mpmath import mp
  1998. for i, j in enumerate(reversed(p)):
  1999. if mpm:
  2000. j = sympify(j)._to_mpmath(mp.prec)
  2001. sol_p += j * x0**i
  2002. for i, j in enumerate(reversed(q)):
  2003. if mpm:
  2004. j = sympify(j)._to_mpmath(mp.prec)
  2005. sol_q += j * x0**i
  2006. if isinstance(sol_p, (PolyElement, FracElement)):
  2007. sol_p = sol_p.as_expr()
  2008. if isinstance(sol_q, (PolyElement, FracElement)):
  2009. sol_q = sol_q.as_expr()
  2010. return sol_p / sol_q
  2011. def _convert_poly_rat_alg(func, x, x0=0, y0=None, lenics=None, domain=QQ, initcond=True):
  2012. """
  2013. Converts polynomials, rationals and algebraic functions to holonomic.
  2014. """
  2015. ispoly = func.is_polynomial()
  2016. if not ispoly:
  2017. israt = func.is_rational_function()
  2018. else:
  2019. israt = True
  2020. if not (ispoly or israt):
  2021. basepoly, ratexp = func.as_base_exp()
  2022. if basepoly.is_polynomial() and ratexp.is_Number:
  2023. if isinstance(ratexp, Float):
  2024. ratexp = nsimplify(ratexp)
  2025. m, n = ratexp.p, ratexp.q
  2026. is_alg = True
  2027. else:
  2028. is_alg = False
  2029. else:
  2030. is_alg = True
  2031. if not (ispoly or israt or is_alg):
  2032. return None
  2033. R = domain.old_poly_ring(x)
  2034. _, Dx = DifferentialOperators(R, 'Dx')
  2035. # if the function is constant
  2036. if not func.has(x):
  2037. return HolonomicFunction(Dx, x, 0, [func])
  2038. if ispoly:
  2039. # differential equation satisfied by polynomial
  2040. sol = func * Dx - func.diff(x)
  2041. sol = _normalize(sol.listofpoly, sol.parent, negative=False)
  2042. is_singular = sol.is_singular(x0)
  2043. # try to compute the conditions for singular points
  2044. if y0 is None and x0 == 0 and is_singular:
  2045. rep = R.from_sympy(func).to_list()
  2046. for i, j in enumerate(reversed(rep)):
  2047. if j == 0:
  2048. continue
  2049. coeff = list(reversed(rep))[i:]
  2050. indicial = i
  2051. break
  2052. for i, j in enumerate(coeff):
  2053. if isinstance(j, (PolyElement, FracElement)):
  2054. coeff[i] = j.as_expr()
  2055. y0 = {indicial: S(coeff)}
  2056. elif israt:
  2057. p, q = func.as_numer_denom()
  2058. # differential equation satisfied by rational
  2059. sol = p * q * Dx + p * q.diff(x) - q * p.diff(x)
  2060. sol = _normalize(sol.listofpoly, sol.parent, negative=False)
  2061. elif is_alg:
  2062. sol = n * (x / m) * Dx - 1
  2063. sol = HolonomicFunction(sol, x).composition(basepoly).annihilator
  2064. is_singular = sol.is_singular(x0)
  2065. # try to compute the conditions for singular points
  2066. if y0 is None and x0 == 0 and is_singular and \
  2067. (lenics is None or lenics <= 1):
  2068. rep = R.from_sympy(basepoly).to_list()
  2069. for i, j in enumerate(reversed(rep)):
  2070. if j == 0:
  2071. continue
  2072. if isinstance(j, (PolyElement, FracElement)):
  2073. j = j.as_expr()
  2074. coeff = S(j)**ratexp
  2075. indicial = S(i) * ratexp
  2076. break
  2077. if isinstance(coeff, (PolyElement, FracElement)):
  2078. coeff = coeff.as_expr()
  2079. y0 = {indicial: S([coeff])}
  2080. if y0 or not initcond:
  2081. return HolonomicFunction(sol, x, x0, y0)
  2082. if not lenics:
  2083. lenics = sol.order
  2084. if sol.is_singular(x0):
  2085. r = HolonomicFunction(sol, x, x0)._indicial()
  2086. l = list(r)
  2087. if len(r) == 1 and r[l[0]] == S.One:
  2088. r = l[0]
  2089. g = func / (x - x0)**r
  2090. singular_ics = _find_conditions(g, x, x0, lenics)
  2091. singular_ics = [j / factorial(i) for i, j in enumerate(singular_ics)]
  2092. y0 = {r:singular_ics}
  2093. return HolonomicFunction(sol, x, x0, y0)
  2094. y0 = _find_conditions(func, x, x0, lenics)
  2095. while not y0:
  2096. x0 += 1
  2097. y0 = _find_conditions(func, x, x0, lenics)
  2098. return HolonomicFunction(sol, x, x0, y0)
  2099. def _convert_meijerint(func, x, initcond=True, domain=QQ):
  2100. args = meijerint._rewrite1(func, x)
  2101. if args:
  2102. fac, po, g, _ = args
  2103. else:
  2104. return None
  2105. # lists for sum of meijerg functions
  2106. fac_list = [fac * i[0] for i in g]
  2107. t = po.as_base_exp()
  2108. s = t[1] if t[0] == x else S.Zero
  2109. po_list = [s + i[1] for i in g]
  2110. G_list = [i[2] for i in g]
  2111. # finds meijerg representation of x**s * meijerg(a1 ... ap, b1 ... bq, z)
  2112. def _shift(func, s):
  2113. z = func.args[-1]
  2114. if z.has(I):
  2115. z = z.subs(exp_polar, exp)
  2116. d = z.collect(x, evaluate=False)
  2117. b = list(d)[0]
  2118. a = d[b]
  2119. t = b.as_base_exp()
  2120. b = t[1] if t[0] == x else S.Zero
  2121. r = s / b
  2122. an = (i + r for i in func.args[0][0])
  2123. ap = (i + r for i in func.args[0][1])
  2124. bm = (i + r for i in func.args[1][0])
  2125. bq = (i + r for i in func.args[1][1])
  2126. return a**-r, meijerg((an, ap), (bm, bq), z)
  2127. coeff, m = _shift(G_list[0], po_list[0])
  2128. sol = fac_list[0] * coeff * from_meijerg(m, initcond=initcond, domain=domain)
  2129. # add all the meijerg functions after converting to holonomic
  2130. for i in range(1, len(G_list)):
  2131. coeff, m = _shift(G_list[i], po_list[i])
  2132. sol += fac_list[i] * coeff * from_meijerg(m, initcond=initcond, domain=domain)
  2133. return sol
  2134. def _create_table(table, domain=QQ):
  2135. """
  2136. Creates the look-up table. For a similar implementation
  2137. see meijerint._create_lookup_table.
  2138. """
  2139. def add(formula, annihilator, arg, x0=0, y0=()):
  2140. """
  2141. Adds a formula in the dictionary
  2142. """
  2143. table.setdefault(_mytype(formula, x_1), []).append((formula,
  2144. HolonomicFunction(annihilator, arg, x0, y0)))
  2145. R = domain.old_poly_ring(x_1)
  2146. _, Dx = DifferentialOperators(R, 'Dx')
  2147. # add some basic functions
  2148. add(sin(x_1), Dx**2 + 1, x_1, 0, [0, 1])
  2149. add(cos(x_1), Dx**2 + 1, x_1, 0, [1, 0])
  2150. add(exp(x_1), Dx - 1, x_1, 0, 1)
  2151. add(log(x_1), Dx + x_1*Dx**2, x_1, 1, [0, 1])
  2152. add(erf(x_1), 2*x_1*Dx + Dx**2, x_1, 0, [0, 2/sqrt(pi)])
  2153. add(erfc(x_1), 2*x_1*Dx + Dx**2, x_1, 0, [1, -2/sqrt(pi)])
  2154. add(erfi(x_1), -2*x_1*Dx + Dx**2, x_1, 0, [0, 2/sqrt(pi)])
  2155. add(sinh(x_1), Dx**2 - 1, x_1, 0, [0, 1])
  2156. add(cosh(x_1), Dx**2 - 1, x_1, 0, [1, 0])
  2157. add(sinc(x_1), x_1 + 2*Dx + x_1*Dx**2, x_1)
  2158. add(Si(x_1), x_1*Dx + 2*Dx**2 + x_1*Dx**3, x_1)
  2159. add(Ci(x_1), x_1*Dx + 2*Dx**2 + x_1*Dx**3, x_1)
  2160. add(Shi(x_1), -x_1*Dx + 2*Dx**2 + x_1*Dx**3, x_1)
  2161. def _find_conditions(func, x, x0, order, evalf=False, use_limit=True):
  2162. y0 = []
  2163. for _ in range(order):
  2164. val = func.subs(x, x0)
  2165. if evalf:
  2166. val = val.evalf()
  2167. if use_limit and isinstance(val, NaN):
  2168. val = limit(func, x, x0)
  2169. if val.is_finite is False or isinstance(val, NaN):
  2170. return None
  2171. y0.append(val)
  2172. func = func.diff(x)
  2173. return y0