12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516 |
- """
- Expand Hypergeometric (and Meijer G) functions into named
- special functions.
- The algorithm for doing this uses a collection of lookup tables of
- hypergeometric functions, and various of their properties, to expand
- many hypergeometric functions in terms of special functions.
- It is based on the following paper:
- Kelly B. Roach. Meijer G Function Representations.
- In: Proceedings of the 1997 International Symposium on Symbolic and
- Algebraic Computation, pages 205-211, New York, 1997. ACM.
- It is described in great(er) detail in the Sphinx documentation.
- """
- # SUMMARY OF EXTENSIONS FOR MEIJER G FUNCTIONS
- #
- # o z**rho G(ap, bq; z) = G(ap + rho, bq + rho; z)
- #
- # o denote z*d/dz by D
- #
- # o It is helpful to keep in mind that ap and bq play essentially symmetric
- # roles: G(1/z) has slightly altered parameters, with ap and bq interchanged.
- #
- # o There are four shift operators:
- # A_J = b_J - D, J = 1, ..., n
- # B_J = 1 - a_j + D, J = 1, ..., m
- # C_J = -b_J + D, J = m+1, ..., q
- # D_J = a_J - 1 - D, J = n+1, ..., p
- #
- # A_J, C_J increment b_J
- # B_J, D_J decrement a_J
- #
- # o The corresponding four inverse-shift operators are defined if there
- # is no cancellation. Thus e.g. an index a_J (upper or lower) can be
- # incremented if a_J != b_i for i = 1, ..., q.
- #
- # o Order reduction: if b_j - a_i is a non-negative integer, where
- # j <= m and i > n, the corresponding quotient of gamma functions reduces
- # to a polynomial. Hence the G function can be expressed using a G-function
- # of lower order.
- # Similarly if j > m and i <= n.
- #
- # Secondly, there are paired index theorems [Adamchik, The evaluation of
- # integrals of Bessel functions via G-function identities]. Suppose there
- # are three parameters a, b, c, where a is an a_i, i <= n, b is a b_j,
- # j <= m and c is a denominator parameter (i.e. a_i, i > n or b_j, j > m).
- # Suppose further all three differ by integers.
- # Then the order can be reduced.
- # TODO work this out in detail.
- #
- # o An index quadruple is called suitable if its order cannot be reduced.
- # If there exists a sequence of shift operators transforming one index
- # quadruple into another, we say one is reachable from the other.
- #
- # o Deciding if one index quadruple is reachable from another is tricky. For
- # this reason, we use hand-built routines to match and instantiate formulas.
- #
- from collections import defaultdict
- from itertools import product
- from functools import reduce
- from sympy import SYMPY_DEBUG
- from sympy.core import (S, Dummy, symbols, sympify, Tuple, expand, I, pi, Mul,
- EulerGamma, oo, zoo, expand_func, Add, nan, Expr, Rational)
- from sympy.core.mod import Mod
- from sympy.core.sorting import default_sort_key
- from sympy.functions import (exp, sqrt, root, log, lowergamma, cos,
- besseli, gamma, uppergamma, expint, erf, sin, besselj, Ei, Ci, Si, Shi,
- sinh, cosh, Chi, fresnels, fresnelc, polar_lift, exp_polar, floor, ceiling,
- rf, factorial, lerchphi, Piecewise, re, elliptic_k, elliptic_e)
- from sympy.functions.elementary.complexes import polarify, unpolarify
- from sympy.functions.special.hyper import (hyper, HyperRep_atanh,
- HyperRep_power1, HyperRep_power2, HyperRep_log1, HyperRep_asin1,
- HyperRep_asin2, HyperRep_sqrts1, HyperRep_sqrts2, HyperRep_log2,
- HyperRep_cosasin, HyperRep_sinasin, meijerg)
- from sympy.polys import poly, Poly
- from sympy.simplify.powsimp import powdenest
- from sympy.utilities.iterables import sift
- # function to define "buckets"
- def _mod1(x):
- # TODO see if this can work as Mod(x, 1); this will require
- # different handling of the "buckets" since these need to
- # be sorted and that fails when there is a mixture of
- # integers and expressions with parameters. With the current
- # Mod behavior, Mod(k, 1) == Mod(1, 1) == 0 if k is an integer.
- # Although the sorting can be done with Basic.compare, this may
- # still require different handling of the sorted buckets.
- if x.is_Number:
- return Mod(x, 1)
- c, x = x.as_coeff_Add()
- return Mod(c, 1) + x
- # leave add formulae at the top for easy reference
- def add_formulae(formulae):
- """ Create our knowledge base. """
- from sympy.matrices import Matrix
- a, b, c, z = symbols('a b c, z', cls=Dummy)
- def add(ap, bq, res):
- func = Hyper_Function(ap, bq)
- formulae.append(Formula(func, z, res, (a, b, c)))
- def addb(ap, bq, B, C, M):
- func = Hyper_Function(ap, bq)
- formulae.append(Formula(func, z, None, (a, b, c), B, C, M))
- # Luke, Y. L. (1969), The Special Functions and Their Approximations,
- # Volume 1, section 6.2
- # 0F0
- add((), (), exp(z))
- # 1F0
- add((a, ), (), HyperRep_power1(-a, z))
- # 2F1
- addb((a, a - S.Half), (2*a, ),
- Matrix([HyperRep_power2(a, z),
- HyperRep_power2(a + S.Half, z)/2]),
- Matrix([[1, 0]]),
- Matrix([[(a - S.Half)*z/(1 - z), (S.Half - a)*z/(1 - z)],
- [a/(1 - z), a*(z - 2)/(1 - z)]]))
- addb((1, 1), (2, ),
- Matrix([HyperRep_log1(z), 1]), Matrix([[-1/z, 0]]),
- Matrix([[0, z/(z - 1)], [0, 0]]))
- addb((S.Half, 1), (S('3/2'), ),
- Matrix([HyperRep_atanh(z), 1]),
- Matrix([[1, 0]]),
- Matrix([[Rational(-1, 2), 1/(1 - z)/2], [0, 0]]))
- addb((S.Half, S.Half), (S('3/2'), ),
- Matrix([HyperRep_asin1(z), HyperRep_power1(Rational(-1, 2), z)]),
- Matrix([[1, 0]]),
- Matrix([[Rational(-1, 2), S.Half], [0, z/(1 - z)/2]]))
- addb((a, S.Half + a), (S.Half, ),
- Matrix([HyperRep_sqrts1(-a, z), -HyperRep_sqrts2(-a - S.Half, z)]),
- Matrix([[1, 0]]),
- Matrix([[0, -a],
- [z*(-2*a - 1)/2/(1 - z), S.Half - z*(-2*a - 1)/(1 - z)]]))
- # A. P. Prudnikov, Yu. A. Brychkov and O. I. Marichev (1990).
- # Integrals and Series: More Special Functions, Vol. 3,.
- # Gordon and Breach Science Publisher
- addb([a, -a], [S.Half],
- Matrix([HyperRep_cosasin(a, z), HyperRep_sinasin(a, z)]),
- Matrix([[1, 0]]),
- Matrix([[0, -a], [a*z/(1 - z), 1/(1 - z)/2]]))
- addb([1, 1], [3*S.Half],
- Matrix([HyperRep_asin2(z), 1]), Matrix([[1, 0]]),
- Matrix([[(z - S.Half)/(1 - z), 1/(1 - z)/2], [0, 0]]))
- # Complete elliptic integrals K(z) and E(z), both a 2F1 function
- addb([S.Half, S.Half], [S.One],
- Matrix([elliptic_k(z), elliptic_e(z)]),
- Matrix([[2/pi, 0]]),
- Matrix([[Rational(-1, 2), -1/(2*z-2)],
- [Rational(-1, 2), S.Half]]))
- addb([Rational(-1, 2), S.Half], [S.One],
- Matrix([elliptic_k(z), elliptic_e(z)]),
- Matrix([[0, 2/pi]]),
- Matrix([[Rational(-1, 2), -1/(2*z-2)],
- [Rational(-1, 2), S.Half]]))
- # 3F2
- addb([Rational(-1, 2), 1, 1], [S.Half, 2],
- Matrix([z*HyperRep_atanh(z), HyperRep_log1(z), 1]),
- Matrix([[Rational(-2, 3), -S.One/(3*z), Rational(2, 3)]]),
- Matrix([[S.Half, 0, z/(1 - z)/2],
- [0, 0, z/(z - 1)],
- [0, 0, 0]]))
- # actually the formula for 3/2 is much nicer ...
- addb([Rational(-1, 2), 1, 1], [2, 2],
- Matrix([HyperRep_power1(S.Half, z), HyperRep_log2(z), 1]),
- Matrix([[Rational(4, 9) - 16/(9*z), 4/(3*z), 16/(9*z)]]),
- Matrix([[z/2/(z - 1), 0, 0], [1/(2*(z - 1)), 0, S.Half], [0, 0, 0]]))
- # 1F1
- addb([1], [b], Matrix([z**(1 - b) * exp(z) * lowergamma(b - 1, z), 1]),
- Matrix([[b - 1, 0]]), Matrix([[1 - b + z, 1], [0, 0]]))
- addb([a], [2*a],
- Matrix([z**(S.Half - a)*exp(z/2)*besseli(a - S.Half, z/2)
- * gamma(a + S.Half)/4**(S.Half - a),
- z**(S.Half - a)*exp(z/2)*besseli(a + S.Half, z/2)
- * gamma(a + S.Half)/4**(S.Half - a)]),
- Matrix([[1, 0]]),
- Matrix([[z/2, z/2], [z/2, (z/2 - 2*a)]]))
- mz = polar_lift(-1)*z
- addb([a], [a + 1],
- Matrix([mz**(-a)*a*lowergamma(a, mz), a*exp(z)]),
- Matrix([[1, 0]]),
- Matrix([[-a, 1], [0, z]]))
- # This one is redundant.
- add([Rational(-1, 2)], [S.Half], exp(z) - sqrt(pi*z)*(-I)*erf(I*sqrt(z)))
- # Added to get nice results for Laplace transform of Fresnel functions
- # http://functions.wolfram.com/07.22.03.6437.01
- # Basic rule
- #add([1], [Rational(3, 4), Rational(5, 4)],
- # sqrt(pi) * (cos(2*sqrt(polar_lift(-1)*z))*fresnelc(2*root(polar_lift(-1)*z,4)/sqrt(pi)) +
- # sin(2*sqrt(polar_lift(-1)*z))*fresnels(2*root(polar_lift(-1)*z,4)/sqrt(pi)))
- # / (2*root(polar_lift(-1)*z,4)))
- # Manually tuned rule
- addb([1], [Rational(3, 4), Rational(5, 4)],
- Matrix([ sqrt(pi)*(I*sinh(2*sqrt(z))*fresnels(2*root(z, 4)*exp(I*pi/4)/sqrt(pi))
- + cosh(2*sqrt(z))*fresnelc(2*root(z, 4)*exp(I*pi/4)/sqrt(pi)))
- * exp(-I*pi/4)/(2*root(z, 4)),
- sqrt(pi)*root(z, 4)*(sinh(2*sqrt(z))*fresnelc(2*root(z, 4)*exp(I*pi/4)/sqrt(pi))
- + I*cosh(2*sqrt(z))*fresnels(2*root(z, 4)*exp(I*pi/4)/sqrt(pi)))
- *exp(-I*pi/4)/2,
- 1 ]),
- Matrix([[1, 0, 0]]),
- Matrix([[Rational(-1, 4), 1, Rational(1, 4)],
- [ z, Rational(1, 4), 0],
- [ 0, 0, 0]]))
- # 2F2
- addb([S.Half, a], [Rational(3, 2), a + 1],
- Matrix([a/(2*a - 1)*(-I)*sqrt(pi/z)*erf(I*sqrt(z)),
- a/(2*a - 1)*(polar_lift(-1)*z)**(-a)*
- lowergamma(a, polar_lift(-1)*z),
- a/(2*a - 1)*exp(z)]),
- Matrix([[1, -1, 0]]),
- Matrix([[Rational(-1, 2), 0, 1], [0, -a, 1], [0, 0, z]]))
- # We make a "basis" of four functions instead of three, and give EulerGamma
- # an extra slot (it could just be a coefficient to 1). The advantage is
- # that this way Polys will not see multivariate polynomials (it treats
- # EulerGamma as an indeterminate), which is *way* faster.
- addb([1, 1], [2, 2],
- Matrix([Ei(z) - log(z), exp(z), 1, EulerGamma]),
- Matrix([[1/z, 0, 0, -1/z]]),
- Matrix([[0, 1, -1, 0], [0, z, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]))
- # 0F1
- add((), (S.Half, ), cosh(2*sqrt(z)))
- addb([], [b],
- Matrix([gamma(b)*z**((1 - b)/2)*besseli(b - 1, 2*sqrt(z)),
- gamma(b)*z**(1 - b/2)*besseli(b, 2*sqrt(z))]),
- Matrix([[1, 0]]), Matrix([[0, 1], [z, (1 - b)]]))
- # 0F3
- x = 4*z**Rational(1, 4)
- def fp(a, z):
- return besseli(a, x) + besselj(a, x)
- def fm(a, z):
- return besseli(a, x) - besselj(a, x)
- # TODO branching
- addb([], [S.Half, a, a + S.Half],
- Matrix([fp(2*a - 1, z), fm(2*a, z)*z**Rational(1, 4),
- fm(2*a - 1, z)*sqrt(z), fp(2*a, z)*z**Rational(3, 4)])
- * 2**(-2*a)*gamma(2*a)*z**((1 - 2*a)/4),
- Matrix([[1, 0, 0, 0]]),
- Matrix([[0, 1, 0, 0],
- [0, S.Half - a, 1, 0],
- [0, 0, S.Half, 1],
- [z, 0, 0, 1 - a]]))
- x = 2*(4*z)**Rational(1, 4)*exp_polar(I*pi/4)
- addb([], [a, a + S.Half, 2*a],
- (2*sqrt(polar_lift(-1)*z))**(1 - 2*a)*gamma(2*a)**2 *
- Matrix([besselj(2*a - 1, x)*besseli(2*a - 1, x),
- x*(besseli(2*a, x)*besselj(2*a - 1, x)
- - besseli(2*a - 1, x)*besselj(2*a, x)),
- x**2*besseli(2*a, x)*besselj(2*a, x),
- x**3*(besseli(2*a, x)*besselj(2*a - 1, x)
- + besseli(2*a - 1, x)*besselj(2*a, x))]),
- Matrix([[1, 0, 0, 0]]),
- Matrix([[0, Rational(1, 4), 0, 0],
- [0, (1 - 2*a)/2, Rational(-1, 2), 0],
- [0, 0, 1 - 2*a, Rational(1, 4)],
- [-32*z, 0, 0, 1 - a]]))
- # 1F2
- addb([a], [a - S.Half, 2*a],
- Matrix([z**(S.Half - a)*besseli(a - S.Half, sqrt(z))**2,
- z**(1 - a)*besseli(a - S.Half, sqrt(z))
- *besseli(a - Rational(3, 2), sqrt(z)),
- z**(Rational(3, 2) - a)*besseli(a - Rational(3, 2), sqrt(z))**2]),
- Matrix([[-gamma(a + S.Half)**2/4**(S.Half - a),
- 2*gamma(a - S.Half)*gamma(a + S.Half)/4**(1 - a),
- 0]]),
- Matrix([[1 - 2*a, 1, 0], [z/2, S.Half - a, S.Half], [0, z, 0]]))
- addb([S.Half], [b, 2 - b],
- pi*(1 - b)/sin(pi*b)*
- Matrix([besseli(1 - b, sqrt(z))*besseli(b - 1, sqrt(z)),
- sqrt(z)*(besseli(-b, sqrt(z))*besseli(b - 1, sqrt(z))
- + besseli(1 - b, sqrt(z))*besseli(b, sqrt(z))),
- besseli(-b, sqrt(z))*besseli(b, sqrt(z))]),
- Matrix([[1, 0, 0]]),
- Matrix([[b - 1, S.Half, 0],
- [z, 0, z],
- [0, S.Half, -b]]))
- addb([S.Half], [Rational(3, 2), Rational(3, 2)],
- Matrix([Shi(2*sqrt(z))/2/sqrt(z), sinh(2*sqrt(z))/2/sqrt(z),
- cosh(2*sqrt(z))]),
- Matrix([[1, 0, 0]]),
- Matrix([[Rational(-1, 2), S.Half, 0], [0, Rational(-1, 2), S.Half], [0, 2*z, 0]]))
- # FresnelS
- # Basic rule
- #add([Rational(3, 4)], [Rational(3, 2),Rational(7, 4)], 6*fresnels( exp(pi*I/4)*root(z,4)*2/sqrt(pi) ) / ( pi * (exp(pi*I/4)*root(z,4)*2/sqrt(pi))**3 ) )
- # Manually tuned rule
- addb([Rational(3, 4)], [Rational(3, 2), Rational(7, 4)],
- Matrix(
- [ fresnels(
- exp(
- pi*I/4)*root(
- z, 4)*2/sqrt(
- pi) ) / (
- pi * (exp(pi*I/4)*root(z, 4)*2/sqrt(pi))**3 ),
- sinh(2*sqrt(z))/sqrt(z),
- cosh(2*sqrt(z)) ]),
- Matrix([[6, 0, 0]]),
- Matrix([[Rational(-3, 4), Rational(1, 16), 0],
- [ 0, Rational(-1, 2), 1],
- [ 0, z, 0]]))
- # FresnelC
- # Basic rule
- #add([Rational(1, 4)], [S.Half,Rational(5, 4)], fresnelc( exp(pi*I/4)*root(z,4)*2/sqrt(pi) ) / ( exp(pi*I/4)*root(z,4)*2/sqrt(pi) ) )
- # Manually tuned rule
- addb([Rational(1, 4)], [S.Half, Rational(5, 4)],
- Matrix(
- [ sqrt(
- pi)*exp(
- -I*pi/4)*fresnelc(
- 2*root(z, 4)*exp(I*pi/4)/sqrt(pi))/(2*root(z, 4)),
- cosh(2*sqrt(z)),
- sinh(2*sqrt(z))*sqrt(z) ]),
- Matrix([[1, 0, 0]]),
- Matrix([[Rational(-1, 4), Rational(1, 4), 0 ],
- [ 0, 0, 1 ],
- [ 0, z, S.Half]]))
- # 2F3
- # XXX with this five-parameter formula is pretty slow with the current
- # Formula.find_instantiations (creates 2!*3!*3**(2+3) ~ 3000
- # instantiations ... But it's not too bad.
- addb([a, a + S.Half], [2*a, b, 2*a - b + 1],
- gamma(b)*gamma(2*a - b + 1) * (sqrt(z)/2)**(1 - 2*a) *
- Matrix([besseli(b - 1, sqrt(z))*besseli(2*a - b, sqrt(z)),
- sqrt(z)*besseli(b, sqrt(z))*besseli(2*a - b, sqrt(z)),
- sqrt(z)*besseli(b - 1, sqrt(z))*besseli(2*a - b + 1, sqrt(z)),
- besseli(b, sqrt(z))*besseli(2*a - b + 1, sqrt(z))]),
- Matrix([[1, 0, 0, 0]]),
- Matrix([[0, S.Half, S.Half, 0],
- [z/2, 1 - b, 0, z/2],
- [z/2, 0, b - 2*a, z/2],
- [0, S.Half, S.Half, -2*a]]))
- # (C/f above comment about eulergamma in the basis).
- addb([1, 1], [2, 2, Rational(3, 2)],
- Matrix([Chi(2*sqrt(z)) - log(2*sqrt(z)),
- cosh(2*sqrt(z)), sqrt(z)*sinh(2*sqrt(z)), 1, EulerGamma]),
- Matrix([[1/z, 0, 0, 0, -1/z]]),
- Matrix([[0, S.Half, 0, Rational(-1, 2), 0],
- [0, 0, 1, 0, 0],
- [0, z, S.Half, 0, 0],
- [0, 0, 0, 0, 0],
- [0, 0, 0, 0, 0]]))
- # 3F3
- # This is rule: http://functions.wolfram.com/07.31.03.0134.01
- # Initial reason to add it was a nice solution for
- # integrate(erf(a*z)/z**2, z) and same for erfc and erfi.
- # Basic rule
- # add([1, 1, a], [2, 2, a+1], (a/(z*(a-1)**2)) *
- # (1 - (-z)**(1-a) * (gamma(a) - uppergamma(a,-z))
- # - (a-1) * (EulerGamma + uppergamma(0,-z) + log(-z))
- # - exp(z)))
- # Manually tuned rule
- addb([1, 1, a], [2, 2, a+1],
- Matrix([a*(log(-z) + expint(1, -z) + EulerGamma)/(z*(a**2 - 2*a + 1)),
- a*(-z)**(-a)*(gamma(a) - uppergamma(a, -z))/(a - 1)**2,
- a*exp(z)/(a**2 - 2*a + 1),
- a/(z*(a**2 - 2*a + 1))]),
- Matrix([[1-a, 1, -1/z, 1]]),
- Matrix([[-1,0,-1/z,1],
- [0,-a,1,0],
- [0,0,z,0],
- [0,0,0,-1]]))
- def add_meijerg_formulae(formulae):
- from sympy.matrices import Matrix
- a, b, c, z = list(map(Dummy, 'abcz'))
- rho = Dummy('rho')
- def add(an, ap, bm, bq, B, C, M, matcher):
- formulae.append(MeijerFormula(an, ap, bm, bq, z, [a, b, c, rho],
- B, C, M, matcher))
- def detect_uppergamma(func):
- x = func.an[0]
- y, z = func.bm
- swapped = False
- if not _mod1((x - y).simplify()):
- swapped = True
- (y, z) = (z, y)
- if _mod1((x - z).simplify()) or x - z > 0:
- return None
- l = [y, x]
- if swapped:
- l = [x, y]
- return {rho: y, a: x - y}, G_Function([x], [], l, [])
- add([a + rho], [], [rho, a + rho], [],
- Matrix([gamma(1 - a)*z**rho*exp(z)*uppergamma(a, z),
- gamma(1 - a)*z**(a + rho)]),
- Matrix([[1, 0]]),
- Matrix([[rho + z, -1], [0, a + rho]]),
- detect_uppergamma)
- def detect_3113(func):
- """http://functions.wolfram.com/07.34.03.0984.01"""
- x = func.an[0]
- u, v, w = func.bm
- if _mod1((u - v).simplify()) == 0:
- if _mod1((v - w).simplify()) == 0:
- return
- sig = (S.Half, S.Half, S.Zero)
- x1, x2, y = u, v, w
- else:
- if _mod1((x - u).simplify()) == 0:
- sig = (S.Half, S.Zero, S.Half)
- x1, y, x2 = u, v, w
- else:
- sig = (S.Zero, S.Half, S.Half)
- y, x1, x2 = u, v, w
- if (_mod1((x - x1).simplify()) != 0 or
- _mod1((x - x2).simplify()) != 0 or
- _mod1((x - y).simplify()) != S.Half or
- x - x1 > 0 or x - x2 > 0):
- return
- return {a: x}, G_Function([x], [], [x - S.Half + t for t in sig], [])
- s = sin(2*sqrt(z))
- c_ = cos(2*sqrt(z))
- S_ = Si(2*sqrt(z)) - pi/2
- C = Ci(2*sqrt(z))
- add([a], [], [a, a, a - S.Half], [],
- Matrix([sqrt(pi)*z**(a - S.Half)*(c_*S_ - s*C),
- sqrt(pi)*z**a*(s*S_ + c_*C),
- sqrt(pi)*z**a]),
- Matrix([[-2, 0, 0]]),
- Matrix([[a - S.Half, -1, 0], [z, a, S.Half], [0, 0, a]]),
- detect_3113)
- def make_simp(z):
- """ Create a function that simplifies rational functions in ``z``. """
- def simp(expr):
- """ Efficiently simplify the rational function ``expr``. """
- numer, denom = expr.as_numer_denom()
- numer = numer.expand()
- # denom = denom.expand() # is this needed?
- c, numer, denom = poly(numer, z).cancel(poly(denom, z))
- return c * numer.as_expr() / denom.as_expr()
- return simp
- def debug(*args):
- if SYMPY_DEBUG:
- for a in args:
- print(a, end="")
- print()
- class Hyper_Function(Expr):
- """ A generalized hypergeometric function. """
- def __new__(cls, ap, bq):
- obj = super().__new__(cls)
- obj.ap = Tuple(*list(map(expand, ap)))
- obj.bq = Tuple(*list(map(expand, bq)))
- return obj
- @property
- def args(self):
- return (self.ap, self.bq)
- @property
- def sizes(self):
- return (len(self.ap), len(self.bq))
- @property
- def gamma(self):
- """
- Number of upper parameters that are negative integers
- This is a transformation invariant.
- """
- return sum(bool(x.is_integer and x.is_negative) for x in self.ap)
- def _hashable_content(self):
- return super()._hashable_content() + (self.ap,
- self.bq)
- def __call__(self, arg):
- return hyper(self.ap, self.bq, arg)
- def build_invariants(self):
- """
- Compute the invariant vector.
- Explanation
- ===========
- The invariant vector is:
- (gamma, ((s1, n1), ..., (sk, nk)), ((t1, m1), ..., (tr, mr)))
- where gamma is the number of integer a < 0,
- s1 < ... < sk
- nl is the number of parameters a_i congruent to sl mod 1
- t1 < ... < tr
- ml is the number of parameters b_i congruent to tl mod 1
- If the index pair contains parameters, then this is not truly an
- invariant, since the parameters cannot be sorted uniquely mod1.
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import Hyper_Function
- >>> from sympy import S
- >>> ap = (S.Half, S.One/3, S(-1)/2, -2)
- >>> bq = (1, 2)
- Here gamma = 1,
- k = 3, s1 = 0, s2 = 1/3, s3 = 1/2
- n1 = 1, n2 = 1, n2 = 2
- r = 1, t1 = 0
- m1 = 2:
- >>> Hyper_Function(ap, bq).build_invariants()
- (1, ((0, 1), (1/3, 1), (1/2, 2)), ((0, 2),))
- """
- abuckets, bbuckets = sift(self.ap, _mod1), sift(self.bq, _mod1)
- def tr(bucket):
- bucket = list(bucket.items())
- if not any(isinstance(x[0], Mod) for x in bucket):
- bucket.sort(key=lambda x: default_sort_key(x[0]))
- bucket = tuple([(mod, len(values)) for mod, values in bucket if
- values])
- return bucket
- return (self.gamma, tr(abuckets), tr(bbuckets))
- def difficulty(self, func):
- """ Estimate how many steps it takes to reach ``func`` from self.
- Return -1 if impossible. """
- if self.gamma != func.gamma:
- return -1
- oabuckets, obbuckets, abuckets, bbuckets = [sift(params, _mod1) for
- params in (self.ap, self.bq, func.ap, func.bq)]
- diff = 0
- for bucket, obucket in [(abuckets, oabuckets), (bbuckets, obbuckets)]:
- for mod in set(list(bucket.keys()) + list(obucket.keys())):
- if (mod not in bucket) or (mod not in obucket) \
- or len(bucket[mod]) != len(obucket[mod]):
- return -1
- l1 = list(bucket[mod])
- l2 = list(obucket[mod])
- l1.sort()
- l2.sort()
- for i, j in zip(l1, l2):
- diff += abs(i - j)
- return diff
- def _is_suitable_origin(self):
- """
- Decide if ``self`` is a suitable origin.
- Explanation
- ===========
- A function is a suitable origin iff:
- * none of the ai equals bj + n, with n a non-negative integer
- * none of the ai is zero
- * none of the bj is a non-positive integer
- Note that this gives meaningful results only when none of the indices
- are symbolic.
- """
- for a in self.ap:
- for b in self.bq:
- if (a - b).is_integer and (a - b).is_negative is False:
- return False
- for a in self.ap:
- if a == 0:
- return False
- for b in self.bq:
- if b.is_integer and b.is_nonpositive:
- return False
- return True
- class G_Function(Expr):
- """ A Meijer G-function. """
- def __new__(cls, an, ap, bm, bq):
- obj = super().__new__(cls)
- obj.an = Tuple(*list(map(expand, an)))
- obj.ap = Tuple(*list(map(expand, ap)))
- obj.bm = Tuple(*list(map(expand, bm)))
- obj.bq = Tuple(*list(map(expand, bq)))
- return obj
- @property
- def args(self):
- return (self.an, self.ap, self.bm, self.bq)
- def _hashable_content(self):
- return super()._hashable_content() + self.args
- def __call__(self, z):
- return meijerg(self.an, self.ap, self.bm, self.bq, z)
- def compute_buckets(self):
- """
- Compute buckets for the fours sets of parameters.
- Explanation
- ===========
- We guarantee that any two equal Mod objects returned are actually the
- same, and that the buckets are sorted by real part (an and bq
- descendending, bm and ap ascending).
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import G_Function
- >>> from sympy.abc import y
- >>> from sympy import S
- >>> a, b = [1, 3, 2, S(3)/2], [1 + y, y, 2, y + 3]
- >>> G_Function(a, b, [2], [y]).compute_buckets()
- ({0: [3, 2, 1], 1/2: [3/2]},
- {0: [2], y: [y, y + 1, y + 3]}, {0: [2]}, {y: [y]})
- """
- dicts = pan, pap, pbm, pbq = [defaultdict(list) for i in range(4)]
- for dic, lis in zip(dicts, (self.an, self.ap, self.bm, self.bq)):
- for x in lis:
- dic[_mod1(x)].append(x)
- for dic, flip in zip(dicts, (True, False, False, True)):
- for m, items in dic.items():
- x0 = items[0]
- items.sort(key=lambda x: x - x0, reverse=flip)
- dic[m] = items
- return tuple([dict(w) for w in dicts])
- @property
- def signature(self):
- return (len(self.an), len(self.ap), len(self.bm), len(self.bq))
- # Dummy variable.
- _x = Dummy('x')
- class Formula:
- """
- This class represents hypergeometric formulae.
- Explanation
- ===========
- Its data members are:
- - z, the argument
- - closed_form, the closed form expression
- - symbols, the free symbols (parameters) in the formula
- - func, the function
- - B, C, M (see _compute_basis)
- Examples
- ========
- >>> from sympy.abc import a, b, z
- >>> from sympy.simplify.hyperexpand import Formula, Hyper_Function
- >>> func = Hyper_Function((a/2, a/3 + b, (1+a)/2), (a, b, (a+b)/7))
- >>> f = Formula(func, z, None, [a, b])
- """
- def _compute_basis(self, closed_form):
- """
- Compute a set of functions B=(f1, ..., fn), a nxn matrix M
- and a 1xn matrix C such that:
- closed_form = C B
- z d/dz B = M B.
- """
- from sympy.matrices import Matrix, eye, zeros
- afactors = [_x + a for a in self.func.ap]
- bfactors = [_x + b - 1 for b in self.func.bq]
- expr = _x*Mul(*bfactors) - self.z*Mul(*afactors)
- poly = Poly(expr, _x)
- n = poly.degree() - 1
- b = [closed_form]
- for _ in range(n):
- b.append(self.z*b[-1].diff(self.z))
- self.B = Matrix(b)
- self.C = Matrix([[1] + [0]*n])
- m = eye(n)
- m = m.col_insert(0, zeros(n, 1))
- l = poly.all_coeffs()[1:]
- l.reverse()
- self.M = m.row_insert(n, -Matrix([l])/poly.all_coeffs()[0])
- def __init__(self, func, z, res, symbols, B=None, C=None, M=None):
- z = sympify(z)
- res = sympify(res)
- symbols = [x for x in sympify(symbols) if func.has(x)]
- self.z = z
- self.symbols = symbols
- self.B = B
- self.C = C
- self.M = M
- self.func = func
- # TODO with symbolic parameters, it could be advantageous
- # (for prettier answers) to compute a basis only *after*
- # instantiation
- if res is not None:
- self._compute_basis(res)
- @property
- def closed_form(self):
- return reduce(lambda s,m: s+m[0]*m[1], zip(self.C, self.B), S.Zero)
- def find_instantiations(self, func):
- """
- Find substitutions of the free symbols that match ``func``.
- Return the substitution dictionaries as a list. Note that the returned
- instantiations need not actually match, or be valid!
- """
- from sympy.solvers import solve
- ap = func.ap
- bq = func.bq
- if len(ap) != len(self.func.ap) or len(bq) != len(self.func.bq):
- raise TypeError('Cannot instantiate other number of parameters')
- symbol_values = []
- for a in self.symbols:
- if a in self.func.ap.args:
- symbol_values.append(ap)
- elif a in self.func.bq.args:
- symbol_values.append(bq)
- else:
- raise ValueError("At least one of the parameters of the "
- "formula must be equal to %s" % (a,))
- base_repl = [dict(list(zip(self.symbols, values)))
- for values in product(*symbol_values)]
- abuckets, bbuckets = [sift(params, _mod1) for params in [ap, bq]]
- a_inv, b_inv = [{a: len(vals) for a, vals in bucket.items()}
- for bucket in [abuckets, bbuckets]]
- critical_values = [[0] for _ in self.symbols]
- result = []
- _n = Dummy()
- for repl in base_repl:
- symb_a, symb_b = [sift(params, lambda x: _mod1(x.xreplace(repl)))
- for params in [self.func.ap, self.func.bq]]
- for bucket, obucket in [(abuckets, symb_a), (bbuckets, symb_b)]:
- for mod in set(list(bucket.keys()) + list(obucket.keys())):
- if (mod not in bucket) or (mod not in obucket) \
- or len(bucket[mod]) != len(obucket[mod]):
- break
- for a, vals in zip(self.symbols, critical_values):
- if repl[a].free_symbols:
- continue
- exprs = [expr for expr in obucket[mod] if expr.has(a)]
- repl0 = repl.copy()
- repl0[a] += _n
- for expr in exprs:
- for target in bucket[mod]:
- n0, = solve(expr.xreplace(repl0) - target, _n)
- if n0.free_symbols:
- raise ValueError("Value should not be true")
- vals.append(n0)
- else:
- values = []
- for a, vals in zip(self.symbols, critical_values):
- a0 = repl[a]
- min_ = floor(min(vals))
- max_ = ceiling(max(vals))
- values.append([a0 + n for n in range(min_, max_ + 1)])
- result.extend(dict(list(zip(self.symbols, l))) for l in product(*values))
- return result
- class FormulaCollection:
- """ A collection of formulae to use as origins. """
- def __init__(self):
- """ Doing this globally at module init time is a pain ... """
- self.symbolic_formulae = {}
- self.concrete_formulae = {}
- self.formulae = []
- add_formulae(self.formulae)
- # Now process the formulae into a helpful form.
- # These dicts are indexed by (p, q).
- for f in self.formulae:
- sizes = f.func.sizes
- if len(f.symbols) > 0:
- self.symbolic_formulae.setdefault(sizes, []).append(f)
- else:
- inv = f.func.build_invariants()
- self.concrete_formulae.setdefault(sizes, {})[inv] = f
- def lookup_origin(self, func):
- """
- Given the suitable target ``func``, try to find an origin in our
- knowledge base.
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import (FormulaCollection,
- ... Hyper_Function)
- >>> f = FormulaCollection()
- >>> f.lookup_origin(Hyper_Function((), ())).closed_form
- exp(_z)
- >>> f.lookup_origin(Hyper_Function([1], ())).closed_form
- HyperRep_power1(-1, _z)
- >>> from sympy import S
- >>> i = Hyper_Function([S('1/4'), S('3/4 + 4')], [S.Half])
- >>> f.lookup_origin(i).closed_form
- HyperRep_sqrts1(-1/4, _z)
- """
- inv = func.build_invariants()
- sizes = func.sizes
- if sizes in self.concrete_formulae and \
- inv in self.concrete_formulae[sizes]:
- return self.concrete_formulae[sizes][inv]
- # We don't have a concrete formula. Try to instantiate.
- if sizes not in self.symbolic_formulae:
- return None # Too bad...
- possible = []
- for f in self.symbolic_formulae[sizes]:
- repls = f.find_instantiations(func)
- for repl in repls:
- func2 = f.func.xreplace(repl)
- if not func2._is_suitable_origin():
- continue
- diff = func2.difficulty(func)
- if diff == -1:
- continue
- possible.append((diff, repl, f, func2))
- # find the nearest origin
- possible.sort(key=lambda x: x[0])
- for _, repl, f, func2 in possible:
- f2 = Formula(func2, f.z, None, [], f.B.subs(repl),
- f.C.subs(repl), f.M.subs(repl))
- if not any(e.has(S.NaN, oo, -oo, zoo) for e in [f2.B, f2.M, f2.C]):
- return f2
- return None
- class MeijerFormula:
- """
- This class represents a Meijer G-function formula.
- Its data members are:
- - z, the argument
- - symbols, the free symbols (parameters) in the formula
- - func, the function
- - B, C, M (c/f ordinary Formula)
- """
- def __init__(self, an, ap, bm, bq, z, symbols, B, C, M, matcher):
- an, ap, bm, bq = [Tuple(*list(map(expand, w))) for w in [an, ap, bm, bq]]
- self.func = G_Function(an, ap, bm, bq)
- self.z = z
- self.symbols = symbols
- self._matcher = matcher
- self.B = B
- self.C = C
- self.M = M
- @property
- def closed_form(self):
- return reduce(lambda s,m: s+m[0]*m[1], zip(self.C, self.B), S.Zero)
- def try_instantiate(self, func):
- """
- Try to instantiate the current formula to (almost) match func.
- This uses the _matcher passed on init.
- """
- if func.signature != self.func.signature:
- return None
- res = self._matcher(func)
- if res is not None:
- subs, newfunc = res
- return MeijerFormula(newfunc.an, newfunc.ap, newfunc.bm, newfunc.bq,
- self.z, [],
- self.B.subs(subs), self.C.subs(subs),
- self.M.subs(subs), None)
- class MeijerFormulaCollection:
- """
- This class holds a collection of meijer g formulae.
- """
- def __init__(self):
- formulae = []
- add_meijerg_formulae(formulae)
- self.formulae = defaultdict(list)
- for formula in formulae:
- self.formulae[formula.func.signature].append(formula)
- self.formulae = dict(self.formulae)
- def lookup_origin(self, func):
- """ Try to find a formula that matches func. """
- if func.signature not in self.formulae:
- return None
- for formula in self.formulae[func.signature]:
- res = formula.try_instantiate(func)
- if res is not None:
- return res
- class Operator:
- """
- Base class for operators to be applied to our functions.
- Explanation
- ===========
- These operators are differential operators. They are by convention
- expressed in the variable D = z*d/dz (although this base class does
- not actually care).
- Note that when the operator is applied to an object, we typically do
- *not* blindly differentiate but instead use a different representation
- of the z*d/dz operator (see make_derivative_operator).
- To subclass from this, define a __init__ method that initializes a
- self._poly variable. This variable stores a polynomial. By convention
- the generator is z*d/dz, and acts to the right of all coefficients.
- Thus this poly
- x**2 + 2*z*x + 1
- represents the differential operator
- (z*d/dz)**2 + 2*z**2*d/dz.
- This class is used only in the implementation of the hypergeometric
- function expansion algorithm.
- """
- def apply(self, obj, op):
- """
- Apply ``self`` to the object ``obj``, where the generator is ``op``.
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import Operator
- >>> from sympy.polys.polytools import Poly
- >>> from sympy.abc import x, y, z
- >>> op = Operator()
- >>> op._poly = Poly(x**2 + z*x + y, x)
- >>> op.apply(z**7, lambda f: f.diff(z))
- y*z**7 + 7*z**7 + 42*z**5
- """
- coeffs = self._poly.all_coeffs()
- coeffs.reverse()
- diffs = [obj]
- for c in coeffs[1:]:
- diffs.append(op(diffs[-1]))
- r = coeffs[0]*diffs[0]
- for c, d in zip(coeffs[1:], diffs[1:]):
- r += c*d
- return r
- class MultOperator(Operator):
- """ Simply multiply by a "constant" """
- def __init__(self, p):
- self._poly = Poly(p, _x)
- class ShiftA(Operator):
- """ Increment an upper index. """
- def __init__(self, ai):
- ai = sympify(ai)
- if ai == 0:
- raise ValueError('Cannot increment zero upper index.')
- self._poly = Poly(_x/ai + 1, _x)
- def __str__(self):
- return '<Increment upper %s.>' % (1/self._poly.all_coeffs()[0])
- class ShiftB(Operator):
- """ Decrement a lower index. """
- def __init__(self, bi):
- bi = sympify(bi)
- if bi == 1:
- raise ValueError('Cannot decrement unit lower index.')
- self._poly = Poly(_x/(bi - 1) + 1, _x)
- def __str__(self):
- return '<Decrement lower %s.>' % (1/self._poly.all_coeffs()[0] + 1)
- class UnShiftA(Operator):
- """ Decrement an upper index. """
- def __init__(self, ap, bq, i, z):
- """ Note: i counts from zero! """
- ap, bq, i = list(map(sympify, [ap, bq, i]))
- self._ap = ap
- self._bq = bq
- self._i = i
- ap = list(ap)
- bq = list(bq)
- ai = ap.pop(i) - 1
- if ai == 0:
- raise ValueError('Cannot decrement unit upper index.')
- m = Poly(z*ai, _x)
- for a in ap:
- m *= Poly(_x + a, _x)
- A = Dummy('A')
- n = D = Poly(ai*A - ai, A)
- for b in bq:
- n *= D + (b - 1).as_poly(A)
- b0 = -n.nth(0)
- if b0 == 0:
- raise ValueError('Cannot decrement upper index: '
- 'cancels with lower')
- n = Poly(Poly(n.all_coeffs()[:-1], A).as_expr().subs(A, _x/ai + 1), _x)
- self._poly = Poly((n - m)/b0, _x)
- def __str__(self):
- return '<Decrement upper index #%s of %s, %s.>' % (self._i,
- self._ap, self._bq)
- class UnShiftB(Operator):
- """ Increment a lower index. """
- def __init__(self, ap, bq, i, z):
- """ Note: i counts from zero! """
- ap, bq, i = list(map(sympify, [ap, bq, i]))
- self._ap = ap
- self._bq = bq
- self._i = i
- ap = list(ap)
- bq = list(bq)
- bi = bq.pop(i) + 1
- if bi == 0:
- raise ValueError('Cannot increment -1 lower index.')
- m = Poly(_x*(bi - 1), _x)
- for b in bq:
- m *= Poly(_x + b - 1, _x)
- B = Dummy('B')
- D = Poly((bi - 1)*B - bi + 1, B)
- n = Poly(z, B)
- for a in ap:
- n *= (D + a.as_poly(B))
- b0 = n.nth(0)
- if b0 == 0:
- raise ValueError('Cannot increment index: cancels with upper')
- n = Poly(Poly(n.all_coeffs()[:-1], B).as_expr().subs(
- B, _x/(bi - 1) + 1), _x)
- self._poly = Poly((m - n)/b0, _x)
- def __str__(self):
- return '<Increment lower index #%s of %s, %s.>' % (self._i,
- self._ap, self._bq)
- class MeijerShiftA(Operator):
- """ Increment an upper b index. """
- def __init__(self, bi):
- bi = sympify(bi)
- self._poly = Poly(bi - _x, _x)
- def __str__(self):
- return '<Increment upper b=%s.>' % (self._poly.all_coeffs()[1])
- class MeijerShiftB(Operator):
- """ Decrement an upper a index. """
- def __init__(self, bi):
- bi = sympify(bi)
- self._poly = Poly(1 - bi + _x, _x)
- def __str__(self):
- return '<Decrement upper a=%s.>' % (1 - self._poly.all_coeffs()[1])
- class MeijerShiftC(Operator):
- """ Increment a lower b index. """
- def __init__(self, bi):
- bi = sympify(bi)
- self._poly = Poly(-bi + _x, _x)
- def __str__(self):
- return '<Increment lower b=%s.>' % (-self._poly.all_coeffs()[1])
- class MeijerShiftD(Operator):
- """ Decrement a lower a index. """
- def __init__(self, bi):
- bi = sympify(bi)
- self._poly = Poly(bi - 1 - _x, _x)
- def __str__(self):
- return '<Decrement lower a=%s.>' % (self._poly.all_coeffs()[1] + 1)
- class MeijerUnShiftA(Operator):
- """ Decrement an upper b index. """
- def __init__(self, an, ap, bm, bq, i, z):
- """ Note: i counts from zero! """
- an, ap, bm, bq, i = list(map(sympify, [an, ap, bm, bq, i]))
- self._an = an
- self._ap = ap
- self._bm = bm
- self._bq = bq
- self._i = i
- an = list(an)
- ap = list(ap)
- bm = list(bm)
- bq = list(bq)
- bi = bm.pop(i) - 1
- m = Poly(1, _x)
- for b in bm:
- m *= Poly(b - _x, _x)
- for b in bq:
- m *= Poly(_x - b, _x)
- A = Dummy('A')
- D = Poly(bi - A, A)
- n = Poly(z, A)
- for a in an:
- n *= (D + 1 - a)
- for a in ap:
- n *= (-D + a - 1)
- b0 = n.nth(0)
- if b0 == 0:
- raise ValueError('Cannot decrement upper b index (cancels)')
- n = Poly(Poly(n.all_coeffs()[:-1], A).as_expr().subs(A, bi - _x), _x)
- self._poly = Poly((m - n)/b0, _x)
- def __str__(self):
- return '<Decrement upper b index #%s of %s, %s, %s, %s.>' % (self._i,
- self._an, self._ap, self._bm, self._bq)
- class MeijerUnShiftB(Operator):
- """ Increment an upper a index. """
- def __init__(self, an, ap, bm, bq, i, z):
- """ Note: i counts from zero! """
- an, ap, bm, bq, i = list(map(sympify, [an, ap, bm, bq, i]))
- self._an = an
- self._ap = ap
- self._bm = bm
- self._bq = bq
- self._i = i
- an = list(an)
- ap = list(ap)
- bm = list(bm)
- bq = list(bq)
- ai = an.pop(i) + 1
- m = Poly(z, _x)
- for a in an:
- m *= Poly(1 - a + _x, _x)
- for a in ap:
- m *= Poly(a - 1 - _x, _x)
- B = Dummy('B')
- D = Poly(B + ai - 1, B)
- n = Poly(1, B)
- for b in bm:
- n *= (-D + b)
- for b in bq:
- n *= (D - b)
- b0 = n.nth(0)
- if b0 == 0:
- raise ValueError('Cannot increment upper a index (cancels)')
- n = Poly(Poly(n.all_coeffs()[:-1], B).as_expr().subs(
- B, 1 - ai + _x), _x)
- self._poly = Poly((m - n)/b0, _x)
- def __str__(self):
- return '<Increment upper a index #%s of %s, %s, %s, %s.>' % (self._i,
- self._an, self._ap, self._bm, self._bq)
- class MeijerUnShiftC(Operator):
- """ Decrement a lower b index. """
- # XXX this is "essentially" the same as MeijerUnShiftA. This "essentially"
- # can be made rigorous using the functional equation G(1/z) = G'(z),
- # where G' denotes a G function of slightly altered parameters.
- # However, sorting out the details seems harder than just coding it
- # again.
- def __init__(self, an, ap, bm, bq, i, z):
- """ Note: i counts from zero! """
- an, ap, bm, bq, i = list(map(sympify, [an, ap, bm, bq, i]))
- self._an = an
- self._ap = ap
- self._bm = bm
- self._bq = bq
- self._i = i
- an = list(an)
- ap = list(ap)
- bm = list(bm)
- bq = list(bq)
- bi = bq.pop(i) - 1
- m = Poly(1, _x)
- for b in bm:
- m *= Poly(b - _x, _x)
- for b in bq:
- m *= Poly(_x - b, _x)
- C = Dummy('C')
- D = Poly(bi + C, C)
- n = Poly(z, C)
- for a in an:
- n *= (D + 1 - a)
- for a in ap:
- n *= (-D + a - 1)
- b0 = n.nth(0)
- if b0 == 0:
- raise ValueError('Cannot decrement lower b index (cancels)')
- n = Poly(Poly(n.all_coeffs()[:-1], C).as_expr().subs(C, _x - bi), _x)
- self._poly = Poly((m - n)/b0, _x)
- def __str__(self):
- return '<Decrement lower b index #%s of %s, %s, %s, %s.>' % (self._i,
- self._an, self._ap, self._bm, self._bq)
- class MeijerUnShiftD(Operator):
- """ Increment a lower a index. """
- # XXX This is essentially the same as MeijerUnShiftA.
- # See comment at MeijerUnShiftC.
- def __init__(self, an, ap, bm, bq, i, z):
- """ Note: i counts from zero! """
- an, ap, bm, bq, i = list(map(sympify, [an, ap, bm, bq, i]))
- self._an = an
- self._ap = ap
- self._bm = bm
- self._bq = bq
- self._i = i
- an = list(an)
- ap = list(ap)
- bm = list(bm)
- bq = list(bq)
- ai = ap.pop(i) + 1
- m = Poly(z, _x)
- for a in an:
- m *= Poly(1 - a + _x, _x)
- for a in ap:
- m *= Poly(a - 1 - _x, _x)
- B = Dummy('B') # - this is the shift operator `D_I`
- D = Poly(ai - 1 - B, B)
- n = Poly(1, B)
- for b in bm:
- n *= (-D + b)
- for b in bq:
- n *= (D - b)
- b0 = n.nth(0)
- if b0 == 0:
- raise ValueError('Cannot increment lower a index (cancels)')
- n = Poly(Poly(n.all_coeffs()[:-1], B).as_expr().subs(
- B, ai - 1 - _x), _x)
- self._poly = Poly((m - n)/b0, _x)
- def __str__(self):
- return '<Increment lower a index #%s of %s, %s, %s, %s.>' % (self._i,
- self._an, self._ap, self._bm, self._bq)
- class ReduceOrder(Operator):
- """ Reduce Order by cancelling an upper and a lower index. """
- def __new__(cls, ai, bj):
- """ For convenience if reduction is not possible, return None. """
- ai = sympify(ai)
- bj = sympify(bj)
- n = ai - bj
- if not n.is_Integer or n < 0:
- return None
- if bj.is_integer and bj.is_nonpositive:
- return None
- expr = Operator.__new__(cls)
- p = S.One
- for k in range(n):
- p *= (_x + bj + k)/(bj + k)
- expr._poly = Poly(p, _x)
- expr._a = ai
- expr._b = bj
- return expr
- @classmethod
- def _meijer(cls, b, a, sign):
- """ Cancel b + sign*s and a + sign*s
- This is for meijer G functions. """
- b = sympify(b)
- a = sympify(a)
- n = b - a
- if n.is_negative or not n.is_Integer:
- return None
- expr = Operator.__new__(cls)
- p = S.One
- for k in range(n):
- p *= (sign*_x + a + k)
- expr._poly = Poly(p, _x)
- if sign == -1:
- expr._a = b
- expr._b = a
- else:
- expr._b = Add(1, a - 1, evaluate=False)
- expr._a = Add(1, b - 1, evaluate=False)
- return expr
- @classmethod
- def meijer_minus(cls, b, a):
- return cls._meijer(b, a, -1)
- @classmethod
- def meijer_plus(cls, a, b):
- return cls._meijer(1 - a, 1 - b, 1)
- def __str__(self):
- return '<Reduce order by cancelling upper %s with lower %s.>' % \
- (self._a, self._b)
- def _reduce_order(ap, bq, gen, key):
- """ Order reduction algorithm used in Hypergeometric and Meijer G """
- ap = list(ap)
- bq = list(bq)
- ap.sort(key=key)
- bq.sort(key=key)
- nap = []
- # we will edit bq in place
- operators = []
- for a in ap:
- op = None
- for i in range(len(bq)):
- op = gen(a, bq[i])
- if op is not None:
- bq.pop(i)
- break
- if op is None:
- nap.append(a)
- else:
- operators.append(op)
- return nap, bq, operators
- def reduce_order(func):
- """
- Given the hypergeometric function ``func``, find a sequence of operators to
- reduces order as much as possible.
- Explanation
- ===========
- Return (newfunc, [operators]), where applying the operators to the
- hypergeometric function newfunc yields func.
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import reduce_order, Hyper_Function
- >>> reduce_order(Hyper_Function((1, 2), (3, 4)))
- (Hyper_Function((1, 2), (3, 4)), [])
- >>> reduce_order(Hyper_Function((1,), (1,)))
- (Hyper_Function((), ()), [<Reduce order by cancelling upper 1 with lower 1.>])
- >>> reduce_order(Hyper_Function((2, 4), (3, 3)))
- (Hyper_Function((2,), (3,)), [<Reduce order by cancelling
- upper 4 with lower 3.>])
- """
- nap, nbq, operators = _reduce_order(func.ap, func.bq, ReduceOrder, default_sort_key)
- return Hyper_Function(Tuple(*nap), Tuple(*nbq)), operators
- def reduce_order_meijer(func):
- """
- Given the Meijer G function parameters, ``func``, find a sequence of
- operators that reduces order as much as possible.
- Return newfunc, [operators].
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import (reduce_order_meijer,
- ... G_Function)
- >>> reduce_order_meijer(G_Function([3, 4], [5, 6], [3, 4], [1, 2]))[0]
- G_Function((4, 3), (5, 6), (3, 4), (2, 1))
- >>> reduce_order_meijer(G_Function([3, 4], [5, 6], [3, 4], [1, 8]))[0]
- G_Function((3,), (5, 6), (3, 4), (1,))
- >>> reduce_order_meijer(G_Function([3, 4], [5, 6], [7, 5], [1, 5]))[0]
- G_Function((3,), (), (), (1,))
- >>> reduce_order_meijer(G_Function([3, 4], [5, 6], [7, 5], [5, 3]))[0]
- G_Function((), (), (), ())
- """
- nan, nbq, ops1 = _reduce_order(func.an, func.bq, ReduceOrder.meijer_plus,
- lambda x: default_sort_key(-x))
- nbm, nap, ops2 = _reduce_order(func.bm, func.ap, ReduceOrder.meijer_minus,
- default_sort_key)
- return G_Function(nan, nap, nbm, nbq), ops1 + ops2
- def make_derivative_operator(M, z):
- """ Create a derivative operator, to be passed to Operator.apply. """
- def doit(C):
- r = z*C.diff(z) + C*M
- r = r.applyfunc(make_simp(z))
- return r
- return doit
- def apply_operators(obj, ops, op):
- """
- Apply the list of operators ``ops`` to object ``obj``, substituting
- ``op`` for the generator.
- """
- res = obj
- for o in reversed(ops):
- res = o.apply(res, op)
- return res
- def devise_plan(target, origin, z):
- """
- Devise a plan (consisting of shift and un-shift operators) to be applied
- to the hypergeometric function ``target`` to yield ``origin``.
- Returns a list of operators.
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import devise_plan, Hyper_Function
- >>> from sympy.abc import z
- Nothing to do:
- >>> devise_plan(Hyper_Function((1, 2), ()), Hyper_Function((1, 2), ()), z)
- []
- >>> devise_plan(Hyper_Function((), (1, 2)), Hyper_Function((), (1, 2)), z)
- []
- Very simple plans:
- >>> devise_plan(Hyper_Function((2,), ()), Hyper_Function((1,), ()), z)
- [<Increment upper 1.>]
- >>> devise_plan(Hyper_Function((), (2,)), Hyper_Function((), (1,)), z)
- [<Increment lower index #0 of [], [1].>]
- Several buckets:
- >>> from sympy import S
- >>> devise_plan(Hyper_Function((1, S.Half), ()),
- ... Hyper_Function((2, S('3/2')), ()), z) #doctest: +NORMALIZE_WHITESPACE
- [<Decrement upper index #0 of [3/2, 1], [].>,
- <Decrement upper index #0 of [2, 3/2], [].>]
- A slightly more complicated plan:
- >>> devise_plan(Hyper_Function((1, 3), ()), Hyper_Function((2, 2), ()), z)
- [<Increment upper 2.>, <Decrement upper index #0 of [2, 2], [].>]
- Another more complicated plan: (note that the ap have to be shifted first!)
- >>> devise_plan(Hyper_Function((1, -1), (2,)), Hyper_Function((3, -2), (4,)), z)
- [<Decrement lower 3.>, <Decrement lower 4.>,
- <Decrement upper index #1 of [-1, 2], [4].>,
- <Decrement upper index #1 of [-1, 3], [4].>, <Increment upper -2.>]
- """
- abuckets, bbuckets, nabuckets, nbbuckets = [sift(params, _mod1) for
- params in (target.ap, target.bq, origin.ap, origin.bq)]
- if len(list(abuckets.keys())) != len(list(nabuckets.keys())) or \
- len(list(bbuckets.keys())) != len(list(nbbuckets.keys())):
- raise ValueError('%s not reachable from %s' % (target, origin))
- ops = []
- def do_shifts(fro, to, inc, dec):
- ops = []
- for i in range(len(fro)):
- if to[i] - fro[i] > 0:
- sh = inc
- ch = 1
- else:
- sh = dec
- ch = -1
- while to[i] != fro[i]:
- ops += [sh(fro, i)]
- fro[i] += ch
- return ops
- def do_shifts_a(nal, nbk, al, aother, bother):
- """ Shift us from (nal, nbk) to (al, nbk). """
- return do_shifts(nal, al, lambda p, i: ShiftA(p[i]),
- lambda p, i: UnShiftA(p + aother, nbk + bother, i, z))
- def do_shifts_b(nal, nbk, bk, aother, bother):
- """ Shift us from (nal, nbk) to (nal, bk). """
- return do_shifts(nbk, bk,
- lambda p, i: UnShiftB(nal + aother, p + bother, i, z),
- lambda p, i: ShiftB(p[i]))
- for r in sorted(list(abuckets.keys()) + list(bbuckets.keys()), key=default_sort_key):
- al = ()
- nal = ()
- bk = ()
- nbk = ()
- if r in abuckets:
- al = abuckets[r]
- nal = nabuckets[r]
- if r in bbuckets:
- bk = bbuckets[r]
- nbk = nbbuckets[r]
- if len(al) != len(nal) or len(bk) != len(nbk):
- raise ValueError('%s not reachable from %s' % (target, origin))
- al, nal, bk, nbk = [sorted(list(w), key=default_sort_key)
- for w in [al, nal, bk, nbk]]
- def others(dic, key):
- l = []
- for k, value in dic.items():
- if k != key:
- l += list(dic[k])
- return l
- aother = others(nabuckets, r)
- bother = others(nbbuckets, r)
- if len(al) == 0:
- # there can be no complications, just shift the bs as we please
- ops += do_shifts_b([], nbk, bk, aother, bother)
- elif len(bk) == 0:
- # there can be no complications, just shift the as as we please
- ops += do_shifts_a(nal, [], al, aother, bother)
- else:
- namax = nal[-1]
- amax = al[-1]
- if nbk[0] - namax <= 0 or bk[0] - amax <= 0:
- raise ValueError('Non-suitable parameters.')
- if namax - amax > 0:
- # we are going to shift down - first do the as, then the bs
- ops += do_shifts_a(nal, nbk, al, aother, bother)
- ops += do_shifts_b(al, nbk, bk, aother, bother)
- else:
- # we are going to shift up - first do the bs, then the as
- ops += do_shifts_b(nal, nbk, bk, aother, bother)
- ops += do_shifts_a(nal, bk, al, aother, bother)
- nabuckets[r] = al
- nbbuckets[r] = bk
- ops.reverse()
- return ops
- def try_shifted_sum(func, z):
- """ Try to recognise a hypergeometric sum that starts from k > 0. """
- abuckets, bbuckets = sift(func.ap, _mod1), sift(func.bq, _mod1)
- if len(abuckets[S.Zero]) != 1:
- return None
- r = abuckets[S.Zero][0]
- if r <= 0:
- return None
- if S.Zero not in bbuckets:
- return None
- l = list(bbuckets[S.Zero])
- l.sort()
- k = l[0]
- if k <= 0:
- return None
- nap = list(func.ap)
- nap.remove(r)
- nbq = list(func.bq)
- nbq.remove(k)
- k -= 1
- nap = [x - k for x in nap]
- nbq = [x - k for x in nbq]
- ops = []
- for n in range(r - 1):
- ops.append(ShiftA(n + 1))
- ops.reverse()
- fac = factorial(k)/z**k
- for a in nap:
- fac /= rf(a, k)
- for b in nbq:
- fac *= rf(b, k)
- ops += [MultOperator(fac)]
- p = 0
- for n in range(k):
- m = z**n/factorial(n)
- for a in nap:
- m *= rf(a, n)
- for b in nbq:
- m /= rf(b, n)
- p += m
- return Hyper_Function(nap, nbq), ops, -p
- def try_polynomial(func, z):
- """ Recognise polynomial cases. Returns None if not such a case.
- Requires order to be fully reduced. """
- abuckets, bbuckets = sift(func.ap, _mod1), sift(func.bq, _mod1)
- a0 = abuckets[S.Zero]
- b0 = bbuckets[S.Zero]
- a0.sort()
- b0.sort()
- al0 = [x for x in a0 if x <= 0]
- bl0 = [x for x in b0 if x <= 0]
- if bl0 and all(a < bl0[-1] for a in al0):
- return oo
- if not al0:
- return None
- a = al0[-1]
- fac = 1
- res = S.One
- for n in Tuple(*list(range(-a))):
- fac *= z
- fac /= n + 1
- for a in func.ap:
- fac *= a + n
- for b in func.bq:
- fac /= b + n
- res += fac
- return res
- def try_lerchphi(func):
- """
- Try to find an expression for Hyper_Function ``func`` in terms of Lerch
- Transcendents.
- Return None if no such expression can be found.
- """
- # This is actually quite simple, and is described in Roach's paper,
- # section 18.
- # We don't need to implement the reduction to polylog here, this
- # is handled by expand_func.
- from sympy.matrices import Matrix, zeros
- from sympy.polys import apart
- # First we need to figure out if the summation coefficient is a rational
- # function of the summation index, and construct that rational function.
- abuckets, bbuckets = sift(func.ap, _mod1), sift(func.bq, _mod1)
- paired = {}
- for key, value in abuckets.items():
- if key != 0 and key not in bbuckets:
- return None
- bvalue = bbuckets[key]
- paired[key] = (list(value), list(bvalue))
- bbuckets.pop(key, None)
- if bbuckets != {}:
- return None
- if S.Zero not in abuckets:
- return None
- aints, bints = paired[S.Zero]
- # Account for the additional n! in denominator
- paired[S.Zero] = (aints, bints + [1])
- t = Dummy('t')
- numer = S.One
- denom = S.One
- for key, (avalue, bvalue) in paired.items():
- if len(avalue) != len(bvalue):
- return None
- # Note that since order has been reduced fully, all the b are
- # bigger than all the a they differ from by an integer. In particular
- # if there are any negative b left, this function is not well-defined.
- for a, b in zip(avalue, bvalue):
- if (a - b).is_positive:
- k = a - b
- numer *= rf(b + t, k)
- denom *= rf(b, k)
- else:
- k = b - a
- numer *= rf(a, k)
- denom *= rf(a + t, k)
- # Now do a partial fraction decomposition.
- # We assemble two structures: a list monomials of pairs (a, b) representing
- # a*t**b (b a non-negative integer), and a dict terms, where
- # terms[a] = [(b, c)] means that there is a term b/(t-a)**c.
- part = apart(numer/denom, t)
- args = Add.make_args(part)
- monomials = []
- terms = {}
- for arg in args:
- numer, denom = arg.as_numer_denom()
- if not denom.has(t):
- p = Poly(numer, t)
- if not p.is_monomial:
- raise TypeError("p should be monomial")
- ((b, ), a) = p.LT()
- monomials += [(a/denom, b)]
- continue
- if numer.has(t):
- raise NotImplementedError('Need partial fraction decomposition'
- ' with linear denominators')
- indep, [dep] = denom.as_coeff_mul(t)
- n = 1
- if dep.is_Pow:
- n = dep.exp
- dep = dep.base
- if dep == t:
- a == 0
- elif dep.is_Add:
- a, tmp = dep.as_independent(t)
- b = 1
- if tmp != t:
- b, _ = tmp.as_independent(t)
- if dep != b*t + a:
- raise NotImplementedError('unrecognised form %s' % dep)
- a /= b
- indep *= b**n
- else:
- raise NotImplementedError('unrecognised form of partial fraction')
- terms.setdefault(a, []).append((numer/indep, n))
- # Now that we have this information, assemble our formula. All the
- # monomials yield rational functions and go into one basis element.
- # The terms[a] are related by differentiation. If the largest exponent is
- # n, we need lerchphi(z, k, a) for k = 1, 2, ..., n.
- # deriv maps a basis to its derivative, expressed as a C(z)-linear
- # combination of other basis elements.
- deriv = {}
- coeffs = {}
- z = Dummy('z')
- monomials.sort(key=lambda x: x[1])
- mon = {0: 1/(1 - z)}
- if monomials:
- for k in range(monomials[-1][1]):
- mon[k + 1] = z*mon[k].diff(z)
- for a, n in monomials:
- coeffs.setdefault(S.One, []).append(a*mon[n])
- for a, l in terms.items():
- for c, k in l:
- coeffs.setdefault(lerchphi(z, k, a), []).append(c)
- l.sort(key=lambda x: x[1])
- for k in range(2, l[-1][1] + 1):
- deriv[lerchphi(z, k, a)] = [(-a, lerchphi(z, k, a)),
- (1, lerchphi(z, k - 1, a))]
- deriv[lerchphi(z, 1, a)] = [(-a, lerchphi(z, 1, a)),
- (1/(1 - z), S.One)]
- trans = {}
- for n, b in enumerate([S.One] + list(deriv.keys())):
- trans[b] = n
- basis = [expand_func(b) for (b, _) in sorted(list(trans.items()),
- key=lambda x:x[1])]
- B = Matrix(basis)
- C = Matrix([[0]*len(B)])
- for b, c in coeffs.items():
- C[trans[b]] = Add(*c)
- M = zeros(len(B))
- for b, l in deriv.items():
- for c, b2 in l:
- M[trans[b], trans[b2]] = c
- return Formula(func, z, None, [], B, C, M)
- def build_hypergeometric_formula(func):
- """
- Create a formula object representing the hypergeometric function ``func``.
- """
- # We know that no `ap` are negative integers, otherwise "detect poly"
- # would have kicked in. However, `ap` could be empty. In this case we can
- # use a different basis.
- # I'm not aware of a basis that works in all cases.
- from sympy.matrices.dense import (Matrix, eye, zeros)
- z = Dummy('z')
- if func.ap:
- afactors = [_x + a for a in func.ap]
- bfactors = [_x + b - 1 for b in func.bq]
- expr = _x*Mul(*bfactors) - z*Mul(*afactors)
- poly = Poly(expr, _x)
- n = poly.degree()
- basis = []
- M = zeros(n)
- for k in range(n):
- a = func.ap[0] + k
- basis += [hyper([a] + list(func.ap[1:]), func.bq, z)]
- if k < n - 1:
- M[k, k] = -a
- M[k, k + 1] = a
- B = Matrix(basis)
- C = Matrix([[1] + [0]*(n - 1)])
- derivs = [eye(n)]
- for k in range(n):
- derivs.append(M*derivs[k])
- l = poly.all_coeffs()
- l.reverse()
- res = [0]*n
- for k, c in enumerate(l):
- for r, d in enumerate(C*derivs[k]):
- res[r] += c*d
- for k, c in enumerate(res):
- M[n - 1, k] = -c/derivs[n - 1][0, n - 1]/poly.all_coeffs()[0]
- return Formula(func, z, None, [], B, C, M)
- else:
- # Since there are no `ap`, none of the `bq` can be non-positive
- # integers.
- basis = []
- bq = list(func.bq[:])
- for i in range(len(bq)):
- basis += [hyper([], bq, z)]
- bq[i] += 1
- basis += [hyper([], bq, z)]
- B = Matrix(basis)
- n = len(B)
- C = Matrix([[1] + [0]*(n - 1)])
- M = zeros(n)
- M[0, n - 1] = z/Mul(*func.bq)
- for k in range(1, n):
- M[k, k - 1] = func.bq[k - 1]
- M[k, k] = -func.bq[k - 1]
- return Formula(func, z, None, [], B, C, M)
- def hyperexpand_special(ap, bq, z):
- """
- Try to find a closed-form expression for hyper(ap, bq, z), where ``z``
- is supposed to be a "special" value, e.g. 1.
- This function tries various of the classical summation formulae
- (Gauss, Saalschuetz, etc).
- """
- # This code is very ad-hoc. There are many clever algorithms
- # (notably Zeilberger's) related to this problem.
- # For now we just want a few simple cases to work.
- p, q = len(ap), len(bq)
- z_ = z
- z = unpolarify(z)
- if z == 0:
- return S.One
- from sympy.simplify.simplify import simplify
- if p == 2 and q == 1:
- # 2F1
- a, b, c = ap + bq
- if z == 1:
- # Gauss
- return gamma(c - a - b)*gamma(c)/gamma(c - a)/gamma(c - b)
- if z == -1 and simplify(b - a + c) == 1:
- b, a = a, b
- if z == -1 and simplify(a - b + c) == 1:
- # Kummer
- if b.is_integer and b.is_negative:
- return 2*cos(pi*b/2)*gamma(-b)*gamma(b - a + 1) \
- /gamma(-b/2)/gamma(b/2 - a + 1)
- else:
- return gamma(b/2 + 1)*gamma(b - a + 1) \
- /gamma(b + 1)/gamma(b/2 - a + 1)
- # TODO tons of more formulae
- # investigate what algorithms exist
- return hyper(ap, bq, z_)
- _collection = None
- def _hyperexpand(func, z, ops0=[], z0=Dummy('z0'), premult=1, prem=0,
- rewrite='default'):
- """
- Try to find an expression for the hypergeometric function ``func``.
- Explanation
- ===========
- The result is expressed in terms of a dummy variable ``z0``. Then it
- is multiplied by ``premult``. Then ``ops0`` is applied.
- ``premult`` must be a*z**prem for some a independent of ``z``.
- """
- if z.is_zero:
- return S.One
- from sympy.simplify.simplify import simplify
- z = polarify(z, subs=False)
- if rewrite == 'default':
- rewrite = 'nonrepsmall'
- def carryout_plan(f, ops):
- C = apply_operators(f.C.subs(f.z, z0), ops,
- make_derivative_operator(f.M.subs(f.z, z0), z0))
- from sympy.matrices.dense import eye
- C = apply_operators(C, ops0,
- make_derivative_operator(f.M.subs(f.z, z0)
- + prem*eye(f.M.shape[0]), z0))
- if premult == 1:
- C = C.applyfunc(make_simp(z0))
- r = reduce(lambda s,m: s+m[0]*m[1], zip(C, f.B.subs(f.z, z0)), S.Zero)*premult
- res = r.subs(z0, z)
- if rewrite:
- res = res.rewrite(rewrite)
- return res
- # TODO
- # The following would be possible:
- # *) PFD Duplication (see Kelly Roach's paper)
- # *) In a similar spirit, try_lerchphi() can be generalised considerably.
- global _collection
- if _collection is None:
- _collection = FormulaCollection()
- debug('Trying to expand hypergeometric function ', func)
- # First reduce order as much as possible.
- func, ops = reduce_order(func)
- if ops:
- debug(' Reduced order to ', func)
- else:
- debug(' Could not reduce order.')
- # Now try polynomial cases
- res = try_polynomial(func, z0)
- if res is not None:
- debug(' Recognised polynomial.')
- p = apply_operators(res, ops, lambda f: z0*f.diff(z0))
- p = apply_operators(p*premult, ops0, lambda f: z0*f.diff(z0))
- return unpolarify(simplify(p).subs(z0, z))
- # Try to recognise a shifted sum.
- p = S.Zero
- res = try_shifted_sum(func, z0)
- if res is not None:
- func, nops, p = res
- debug(' Recognised shifted sum, reduced order to ', func)
- ops += nops
- # apply the plan for poly
- p = apply_operators(p, ops, lambda f: z0*f.diff(z0))
- p = apply_operators(p*premult, ops0, lambda f: z0*f.diff(z0))
- p = simplify(p).subs(z0, z)
- # Try special expansions early.
- if unpolarify(z) in [1, -1] and (len(func.ap), len(func.bq)) == (2, 1):
- f = build_hypergeometric_formula(func)
- r = carryout_plan(f, ops).replace(hyper, hyperexpand_special)
- if not r.has(hyper):
- return r + p
- # Try to find a formula in our collection
- formula = _collection.lookup_origin(func)
- # Now try a lerch phi formula
- if formula is None:
- formula = try_lerchphi(func)
- if formula is None:
- debug(' Could not find an origin. ',
- 'Will return answer in terms of '
- 'simpler hypergeometric functions.')
- formula = build_hypergeometric_formula(func)
- debug(' Found an origin: ', formula.closed_form, ' ', formula.func)
- # We need to find the operators that convert formula into func.
- ops += devise_plan(func, formula.func, z0)
- # Now carry out the plan.
- r = carryout_plan(formula, ops) + p
- return powdenest(r, polar=True).replace(hyper, hyperexpand_special)
- def devise_plan_meijer(fro, to, z):
- """
- Find operators to convert G-function ``fro`` into G-function ``to``.
- Explanation
- ===========
- It is assumed that ``fro`` and ``to`` have the same signatures, and that in fact
- any corresponding pair of parameters differs by integers, and a direct path
- is possible. I.e. if there are parameters a1 b1 c1 and a2 b2 c2 it is
- assumed that a1 can be shifted to a2, etc. The only thing this routine
- determines is the order of shifts to apply, nothing clever will be tried.
- It is also assumed that ``fro`` is suitable.
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import (devise_plan_meijer,
- ... G_Function)
- >>> from sympy.abc import z
- Empty plan:
- >>> devise_plan_meijer(G_Function([1], [2], [3], [4]),
- ... G_Function([1], [2], [3], [4]), z)
- []
- Very simple plans:
- >>> devise_plan_meijer(G_Function([0], [], [], []),
- ... G_Function([1], [], [], []), z)
- [<Increment upper a index #0 of [0], [], [], [].>]
- >>> devise_plan_meijer(G_Function([0], [], [], []),
- ... G_Function([-1], [], [], []), z)
- [<Decrement upper a=0.>]
- >>> devise_plan_meijer(G_Function([], [1], [], []),
- ... G_Function([], [2], [], []), z)
- [<Increment lower a index #0 of [], [1], [], [].>]
- Slightly more complicated plans:
- >>> devise_plan_meijer(G_Function([0], [], [], []),
- ... G_Function([2], [], [], []), z)
- [<Increment upper a index #0 of [1], [], [], [].>,
- <Increment upper a index #0 of [0], [], [], [].>]
- >>> devise_plan_meijer(G_Function([0], [], [0], []),
- ... G_Function([-1], [], [1], []), z)
- [<Increment upper b=0.>, <Decrement upper a=0.>]
- Order matters:
- >>> devise_plan_meijer(G_Function([0], [], [0], []),
- ... G_Function([1], [], [1], []), z)
- [<Increment upper a index #0 of [0], [], [1], [].>, <Increment upper b=0.>]
- """
- # TODO for now, we use the following simple heuristic: inverse-shift
- # when possible, shift otherwise. Give up if we cannot make progress.
- def try_shift(f, t, shifter, diff, counter):
- """ Try to apply ``shifter`` in order to bring some element in ``f``
- nearer to its counterpart in ``to``. ``diff`` is +/- 1 and
- determines the effect of ``shifter``. Counter is a list of elements
- blocking the shift.
- Return an operator if change was possible, else None.
- """
- for idx, (a, b) in enumerate(zip(f, t)):
- if (
- (a - b).is_integer and (b - a)/diff > 0 and
- all(a != x for x in counter)):
- sh = shifter(idx)
- f[idx] += diff
- return sh
- fan = list(fro.an)
- fap = list(fro.ap)
- fbm = list(fro.bm)
- fbq = list(fro.bq)
- ops = []
- change = True
- while change:
- change = False
- op = try_shift(fan, to.an,
- lambda i: MeijerUnShiftB(fan, fap, fbm, fbq, i, z),
- 1, fbm + fbq)
- if op is not None:
- ops += [op]
- change = True
- continue
- op = try_shift(fap, to.ap,
- lambda i: MeijerUnShiftD(fan, fap, fbm, fbq, i, z),
- 1, fbm + fbq)
- if op is not None:
- ops += [op]
- change = True
- continue
- op = try_shift(fbm, to.bm,
- lambda i: MeijerUnShiftA(fan, fap, fbm, fbq, i, z),
- -1, fan + fap)
- if op is not None:
- ops += [op]
- change = True
- continue
- op = try_shift(fbq, to.bq,
- lambda i: MeijerUnShiftC(fan, fap, fbm, fbq, i, z),
- -1, fan + fap)
- if op is not None:
- ops += [op]
- change = True
- continue
- op = try_shift(fan, to.an, lambda i: MeijerShiftB(fan[i]), -1, [])
- if op is not None:
- ops += [op]
- change = True
- continue
- op = try_shift(fap, to.ap, lambda i: MeijerShiftD(fap[i]), -1, [])
- if op is not None:
- ops += [op]
- change = True
- continue
- op = try_shift(fbm, to.bm, lambda i: MeijerShiftA(fbm[i]), 1, [])
- if op is not None:
- ops += [op]
- change = True
- continue
- op = try_shift(fbq, to.bq, lambda i: MeijerShiftC(fbq[i]), 1, [])
- if op is not None:
- ops += [op]
- change = True
- continue
- if fan != list(to.an) or fap != list(to.ap) or fbm != list(to.bm) or \
- fbq != list(to.bq):
- raise NotImplementedError('Could not devise plan.')
- ops.reverse()
- return ops
- _meijercollection = None
- def _meijergexpand(func, z0, allow_hyper=False, rewrite='default',
- place=None):
- """
- Try to find an expression for the Meijer G function specified
- by the G_Function ``func``. If ``allow_hyper`` is True, then returning
- an expression in terms of hypergeometric functions is allowed.
- Currently this just does Slater's theorem.
- If expansions exist both at zero and at infinity, ``place``
- can be set to ``0`` or ``zoo`` for the preferred choice.
- """
- global _meijercollection
- if _meijercollection is None:
- _meijercollection = MeijerFormulaCollection()
- if rewrite == 'default':
- rewrite = None
- func0 = func
- debug('Try to expand Meijer G function corresponding to ', func)
- # We will play games with analytic continuation - rather use a fresh symbol
- z = Dummy('z')
- func, ops = reduce_order_meijer(func)
- if ops:
- debug(' Reduced order to ', func)
- else:
- debug(' Could not reduce order.')
- # Try to find a direct formula
- f = _meijercollection.lookup_origin(func)
- if f is not None:
- debug(' Found a Meijer G formula: ', f.func)
- ops += devise_plan_meijer(f.func, func, z)
- # Now carry out the plan.
- C = apply_operators(f.C.subs(f.z, z), ops,
- make_derivative_operator(f.M.subs(f.z, z), z))
- C = C.applyfunc(make_simp(z))
- r = C*f.B.subs(f.z, z)
- r = r[0].subs(z, z0)
- return powdenest(r, polar=True)
- debug(" Could not find a direct formula. Trying Slater's theorem.")
- # TODO the following would be possible:
- # *) Paired Index Theorems
- # *) PFD Duplication
- # (See Kelly Roach's paper for details on either.)
- #
- # TODO Also, we tend to create combinations of gamma functions that can be
- # simplified.
- def can_do(pbm, pap):
- """ Test if slater applies. """
- for i in pbm:
- if len(pbm[i]) > 1:
- l = 0
- if i in pap:
- l = len(pap[i])
- if l + 1 < len(pbm[i]):
- return False
- return True
- def do_slater(an, bm, ap, bq, z, zfinal):
- # zfinal is the value that will eventually be substituted for z.
- # We pass it to _hyperexpand to improve performance.
- from sympy.series import residue
- func = G_Function(an, bm, ap, bq)
- _, pbm, pap, _ = func.compute_buckets()
- if not can_do(pbm, pap):
- return S.Zero, False
- cond = len(an) + len(ap) < len(bm) + len(bq)
- if len(an) + len(ap) == len(bm) + len(bq):
- cond = abs(z) < 1
- if cond is False:
- return S.Zero, False
- res = S.Zero
- for m in pbm:
- if len(pbm[m]) == 1:
- bh = pbm[m][0]
- fac = 1
- bo = list(bm)
- bo.remove(bh)
- for bj in bo:
- fac *= gamma(bj - bh)
- for aj in an:
- fac *= gamma(1 + bh - aj)
- for bj in bq:
- fac /= gamma(1 + bh - bj)
- for aj in ap:
- fac /= gamma(aj - bh)
- nap = [1 + bh - a for a in list(an) + list(ap)]
- nbq = [1 + bh - b for b in list(bo) + list(bq)]
- k = polar_lift(S.NegativeOne**(len(ap) - len(bm)))
- harg = k*zfinal
- # NOTE even though k "is" +-1, this has to be t/k instead of
- # t*k ... we are using polar numbers for consistency!
- premult = (t/k)**bh
- hyp = _hyperexpand(Hyper_Function(nap, nbq), harg, ops,
- t, premult, bh, rewrite=None)
- res += fac * hyp
- else:
- b_ = pbm[m][0]
- ki = [bi - b_ for bi in pbm[m][1:]]
- u = len(ki)
- li = [ai - b_ for ai in pap[m][:u + 1]]
- bo = list(bm)
- for b in pbm[m]:
- bo.remove(b)
- ao = list(ap)
- for a in pap[m][:u]:
- ao.remove(a)
- lu = li[-1]
- di = [l - k for (l, k) in zip(li, ki)]
- # We first work out the integrand:
- s = Dummy('s')
- integrand = z**s
- for b in bm:
- if not Mod(b, 1) and b.is_Number:
- b = int(round(b))
- integrand *= gamma(b - s)
- for a in an:
- integrand *= gamma(1 - a + s)
- for b in bq:
- integrand /= gamma(1 - b + s)
- for a in ap:
- integrand /= gamma(a - s)
- # Now sum the finitely many residues:
- # XXX This speeds up some cases - is it a good idea?
- integrand = expand_func(integrand)
- for r in range(int(round(lu))):
- resid = residue(integrand, s, b_ + r)
- resid = apply_operators(resid, ops, lambda f: z*f.diff(z))
- res -= resid
- # Now the hypergeometric term.
- au = b_ + lu
- k = polar_lift(S.NegativeOne**(len(ao) + len(bo) + 1))
- harg = k*zfinal
- premult = (t/k)**au
- nap = [1 + au - a for a in list(an) + list(ap)] + [1]
- nbq = [1 + au - b for b in list(bm) + list(bq)]
- hyp = _hyperexpand(Hyper_Function(nap, nbq), harg, ops,
- t, premult, au, rewrite=None)
- C = S.NegativeOne**(lu)/factorial(lu)
- for i in range(u):
- C *= S.NegativeOne**di[i]/rf(lu - li[i] + 1, di[i])
- for a in an:
- C *= gamma(1 - a + au)
- for b in bo:
- C *= gamma(b - au)
- for a in ao:
- C /= gamma(a - au)
- for b in bq:
- C /= gamma(1 - b + au)
- res += C*hyp
- return res, cond
- t = Dummy('t')
- slater1, cond1 = do_slater(func.an, func.bm, func.ap, func.bq, z, z0)
- def tr(l):
- return [1 - x for x in l]
- for op in ops:
- op._poly = Poly(op._poly.subs({z: 1/t, _x: -_x}), _x)
- slater2, cond2 = do_slater(tr(func.bm), tr(func.an), tr(func.bq), tr(func.ap),
- t, 1/z0)
- slater1 = powdenest(slater1.subs(z, z0), polar=True)
- slater2 = powdenest(slater2.subs(t, 1/z0), polar=True)
- if not isinstance(cond2, bool):
- cond2 = cond2.subs(t, 1/z)
- m = func(z)
- if m.delta > 0 or \
- (m.delta == 0 and len(m.ap) == len(m.bq) and
- (re(m.nu) < -1) is not False and polar_lift(z0) == polar_lift(1)):
- # The condition delta > 0 means that the convergence region is
- # connected. Any expression we find can be continued analytically
- # to the entire convergence region.
- # The conditions delta==0, p==q, re(nu) < -1 imply that G is continuous
- # on the positive reals, so the values at z=1 agree.
- if cond1 is not False:
- cond1 = True
- if cond2 is not False:
- cond2 = True
- if cond1 is True:
- slater1 = slater1.rewrite(rewrite or 'nonrep')
- else:
- slater1 = slater1.rewrite(rewrite or 'nonrepsmall')
- if cond2 is True:
- slater2 = slater2.rewrite(rewrite or 'nonrep')
- else:
- slater2 = slater2.rewrite(rewrite or 'nonrepsmall')
- if cond1 is not False and cond2 is not False:
- # If one condition is False, there is no choice.
- if place == 0:
- cond2 = False
- if place == zoo:
- cond1 = False
- if not isinstance(cond1, bool):
- cond1 = cond1.subs(z, z0)
- if not isinstance(cond2, bool):
- cond2 = cond2.subs(z, z0)
- def weight(expr, cond):
- if cond is True:
- c0 = 0
- elif cond is False:
- c0 = 1
- else:
- c0 = 2
- if expr.has(oo, zoo, -oo, nan):
- # XXX this actually should not happen, but consider
- # S('meijerg(((0, -1/2, 0, -1/2, 1/2), ()), ((0,),
- # (-1/2, -1/2, -1/2, -1)), exp_polar(I*pi))/4')
- c0 = 3
- return (c0, expr.count(hyper), expr.count_ops())
- w1 = weight(slater1, cond1)
- w2 = weight(slater2, cond2)
- if min(w1, w2) <= (0, 1, oo):
- if w1 < w2:
- return slater1
- else:
- return slater2
- if max(w1[0], w2[0]) <= 1 and max(w1[1], w2[1]) <= 1:
- return Piecewise((slater1, cond1), (slater2, cond2), (func0(z0), True))
- # We couldn't find an expression without hypergeometric functions.
- # TODO it would be helpful to give conditions under which the integral
- # is known to diverge.
- r = Piecewise((slater1, cond1), (slater2, cond2), (func0(z0), True))
- if r.has(hyper) and not allow_hyper:
- debug(' Could express using hypergeometric functions, '
- 'but not allowed.')
- if not r.has(hyper) or allow_hyper:
- return r
- return func0(z0)
- def hyperexpand(f, allow_hyper=False, rewrite='default', place=None):
- """
- Expand hypergeometric functions. If allow_hyper is True, allow partial
- simplification (that is a result different from input,
- but still containing hypergeometric functions).
- If a G-function has expansions both at zero and at infinity,
- ``place`` can be set to ``0`` or ``zoo`` to indicate the
- preferred choice.
- Examples
- ========
- >>> from sympy.simplify.hyperexpand import hyperexpand
- >>> from sympy.functions import hyper
- >>> from sympy.abc import z
- >>> hyperexpand(hyper([], [], z))
- exp(z)
- Non-hyperegeometric parts of the expression and hypergeometric expressions
- that are not recognised are left unchanged:
- >>> hyperexpand(1 + hyper([1, 1, 1], [], z))
- hyper((1, 1, 1), (), z) + 1
- """
- f = sympify(f)
- def do_replace(ap, bq, z):
- r = _hyperexpand(Hyper_Function(ap, bq), z, rewrite=rewrite)
- if r is None:
- return hyper(ap, bq, z)
- else:
- return r
- def do_meijer(ap, bq, z):
- r = _meijergexpand(G_Function(ap[0], ap[1], bq[0], bq[1]), z,
- allow_hyper, rewrite=rewrite, place=place)
- if not r.has(nan, zoo, oo, -oo):
- return r
- return f.replace(hyper, do_replace).replace(meijerg, do_meijer)
|