holonomic.py 92 KB

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