123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158 |
- """Hypergeometric and Meijer G-functions"""
- from functools import reduce
- from sympy.core import S, I, pi, oo, zoo, ilcm, Mod
- from sympy.core.add import Add
- from sympy.core.expr import Expr
- from sympy.core.function import Function, Derivative, ArgumentIndexError
- from sympy.core.containers import Tuple
- from sympy.core.mul import Mul
- from sympy.core.relational import Ne
- from sympy.core.sorting import default_sort_key
- from sympy.core.symbol import Dummy
- from sympy.functions import (sqrt, exp, log, sin, cos, asin, atan,
- sinh, cosh, asinh, acosh, atanh, acoth, Abs, re, factorial, RisingFactorial)
- from sympy.logic.boolalg import (And, Or)
- class TupleArg(Tuple):
- def limit(self, x, xlim, dir='+'):
- """ Compute limit x->xlim.
- """
- from sympy.series.limits import limit
- return TupleArg(*[limit(f, x, xlim, dir) for f in self.args])
- # TODO should __new__ accept **options?
- # TODO should constructors should check if parameters are sensible?
- def _prep_tuple(v):
- """
- Turn an iterable argument *v* into a tuple and unpolarify, since both
- hypergeometric and meijer g-functions are unbranched in their parameters.
- Examples
- ========
- >>> from sympy.functions.special.hyper import _prep_tuple
- >>> _prep_tuple([1, 2, 3])
- (1, 2, 3)
- >>> _prep_tuple((4, 5))
- (4, 5)
- >>> _prep_tuple((7, 8, 9))
- (7, 8, 9)
- """
- from sympy.functions.elementary.complexes import unpolarify
- return TupleArg(*[unpolarify(x) for x in v])
- class TupleParametersBase(Function):
- """ Base class that takes care of differentiation, when some of
- the arguments are actually tuples. """
- # This is not deduced automatically since there are Tuples as arguments.
- is_commutative = True
- def _eval_derivative(self, s):
- try:
- res = 0
- if self.args[0].has(s) or self.args[1].has(s):
- for i, p in enumerate(self._diffargs):
- m = self._diffargs[i].diff(s)
- if m != 0:
- res += self.fdiff((1, i))*m
- return res + self.fdiff(3)*self.args[2].diff(s)
- except (ArgumentIndexError, NotImplementedError):
- return Derivative(self, s)
- class hyper(TupleParametersBase):
- r"""
- The generalized hypergeometric function is defined by a series where
- the ratios of successive terms are a rational function of the summation
- index. When convergent, it is continued analytically to the largest
- possible domain.
- Explanation
- ===========
- The hypergeometric function depends on two vectors of parameters, called
- the numerator parameters $a_p$, and the denominator parameters
- $b_q$. It also has an argument $z$. The series definition is
- .. math ::
- {}_pF_q\left(\begin{matrix} a_1, \cdots, a_p \\ b_1, \cdots, b_q \end{matrix}
- \middle| z \right)
- = \sum_{n=0}^\infty \frac{(a_1)_n \cdots (a_p)_n}{(b_1)_n \cdots (b_q)_n}
- \frac{z^n}{n!},
- where $(a)_n = (a)(a+1)\cdots(a+n-1)$ denotes the rising factorial.
- If one of the $b_q$ is a non-positive integer then the series is
- undefined unless one of the $a_p$ is a larger (i.e., smaller in
- magnitude) non-positive integer. If none of the $b_q$ is a
- non-positive integer and one of the $a_p$ is a non-positive
- integer, then the series reduces to a polynomial. To simplify the
- following discussion, we assume that none of the $a_p$ or
- $b_q$ is a non-positive integer. For more details, see the
- references.
- The series converges for all $z$ if $p \le q$, and thus
- defines an entire single-valued function in this case. If $p =
- q+1$ the series converges for $|z| < 1$, and can be continued
- analytically into a half-plane. If $p > q+1$ the series is
- divergent for all $z$.
- Please note the hypergeometric function constructor currently does *not*
- check if the parameters actually yield a well-defined function.
- Examples
- ========
- The parameters $a_p$ and $b_q$ can be passed as arbitrary
- iterables, for example:
- >>> from sympy import hyper
- >>> from sympy.abc import x, n, a
- >>> hyper((1, 2, 3), [3, 4], x)
- hyper((1, 2, 3), (3, 4), x)
- There is also pretty printing (it looks better using Unicode):
- >>> from sympy import pprint
- >>> pprint(hyper((1, 2, 3), [3, 4], x), use_unicode=False)
- _
- |_ /1, 2, 3 | \
- | | | x|
- 3 2 \ 3, 4 | /
- The parameters must always be iterables, even if they are vectors of
- length one or zero:
- >>> hyper((1, ), [], x)
- hyper((1,), (), x)
- But of course they may be variables (but if they depend on $x$ then you
- should not expect much implemented functionality):
- >>> hyper((n, a), (n**2,), x)
- hyper((n, a), (n**2,), x)
- The hypergeometric function generalizes many named special functions.
- The function ``hyperexpand()`` tries to express a hypergeometric function
- using named special functions. For example:
- >>> from sympy import hyperexpand
- >>> hyperexpand(hyper([], [], x))
- exp(x)
- You can also use ``expand_func()``:
- >>> from sympy import expand_func
- >>> expand_func(x*hyper([1, 1], [2], -x))
- log(x + 1)
- More examples:
- >>> from sympy import S
- >>> hyperexpand(hyper([], [S(1)/2], -x**2/4))
- cos(x)
- >>> hyperexpand(x*hyper([S(1)/2, S(1)/2], [S(3)/2], x**2))
- asin(x)
- We can also sometimes ``hyperexpand()`` parametric functions:
- >>> from sympy.abc import a
- >>> hyperexpand(hyper([-a], [], x))
- (1 - x)**a
- See Also
- ========
- sympy.simplify.hyperexpand
- gamma
- meijerg
- References
- ==========
- .. [1] Luke, Y. L. (1969), The Special Functions and Their Approximations,
- Volume 1
- .. [2] https://en.wikipedia.org/wiki/Generalized_hypergeometric_function
- """
- def __new__(cls, ap, bq, z, **kwargs):
- # TODO should we check convergence conditions?
- return Function.__new__(cls, _prep_tuple(ap), _prep_tuple(bq), z, **kwargs)
- @classmethod
- def eval(cls, ap, bq, z):
- from sympy.functions.elementary.complexes import unpolarify
- if len(ap) <= len(bq) or (len(ap) == len(bq) + 1 and (Abs(z) <= 1) == True):
- nz = unpolarify(z)
- if z != nz:
- return hyper(ap, bq, nz)
- def fdiff(self, argindex=3):
- if argindex != 3:
- raise ArgumentIndexError(self, argindex)
- nap = Tuple(*[a + 1 for a in self.ap])
- nbq = Tuple(*[b + 1 for b in self.bq])
- fac = Mul(*self.ap)/Mul(*self.bq)
- return fac*hyper(nap, nbq, self.argument)
- def _eval_expand_func(self, **hints):
- from sympy.functions.special.gamma_functions import gamma
- from sympy.simplify.hyperexpand import hyperexpand
- if len(self.ap) == 2 and len(self.bq) == 1 and self.argument == 1:
- a, b = self.ap
- c = self.bq[0]
- return gamma(c)*gamma(c - a - b)/gamma(c - a)/gamma(c - b)
- return hyperexpand(self)
- def _eval_rewrite_as_Sum(self, ap, bq, z, **kwargs):
- from sympy.functions import factorial, RisingFactorial, Piecewise
- from sympy.concrete.summations import Sum
- n = Dummy("n", integer=True)
- rfap = Tuple(*[RisingFactorial(a, n) for a in ap])
- rfbq = Tuple(*[RisingFactorial(b, n) for b in bq])
- coeff = Mul(*rfap) / Mul(*rfbq)
- return Piecewise((Sum(coeff * z**n / factorial(n), (n, 0, oo)),
- self.convergence_statement), (self, True))
- def _eval_as_leading_term(self, x, logx=None, cdir=0):
- arg = self.args[2]
- x0 = arg.subs(x, 0)
- if x0 is S.NaN:
- x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
- if x0 is S.Zero:
- return S.One
- return super()._eval_as_leading_term(x, logx=logx, cdir=cdir)
- def _eval_nseries(self, x, n, logx, cdir=0):
- from sympy.series.order import Order
- arg = self.args[2]
- x0 = arg.limit(x, 0)
- ap = self.args[0]
- bq = self.args[1]
- if x0 != 0:
- return super()._eval_nseries(x, n, logx)
- terms = []
- for i in range(n):
- num = 1
- den = 1
- for a in ap:
- num *= RisingFactorial(a, i)
- for b in bq:
- den *= RisingFactorial(b, i)
- terms.append(((num/den) * (arg**i)) / factorial(i))
- return (Add(*terms) + Order(x**n,x))
- @property
- def argument(self):
- """ Argument of the hypergeometric function. """
- return self.args[2]
- @property
- def ap(self):
- """ Numerator parameters of the hypergeometric function. """
- return Tuple(*self.args[0])
- @property
- def bq(self):
- """ Denominator parameters of the hypergeometric function. """
- return Tuple(*self.args[1])
- @property
- def _diffargs(self):
- return self.ap + self.bq
- @property
- def eta(self):
- """ A quantity related to the convergence of the series. """
- return sum(self.ap) - sum(self.bq)
- @property
- def radius_of_convergence(self):
- """
- Compute the radius of convergence of the defining series.
- Explanation
- ===========
- Note that even if this is not ``oo``, the function may still be
- evaluated outside of the radius of convergence by analytic
- continuation. But if this is zero, then the function is not actually
- defined anywhere else.
- Examples
- ========
- >>> from sympy import hyper
- >>> from sympy.abc import z
- >>> hyper((1, 2), [3], z).radius_of_convergence
- 1
- >>> hyper((1, 2, 3), [4], z).radius_of_convergence
- 0
- >>> hyper((1, 2), (3, 4), z).radius_of_convergence
- oo
- """
- if any(a.is_integer and (a <= 0) == True for a in self.ap + self.bq):
- aints = [a for a in self.ap if a.is_Integer and (a <= 0) == True]
- bints = [a for a in self.bq if a.is_Integer and (a <= 0) == True]
- if len(aints) < len(bints):
- return S.Zero
- popped = False
- for b in bints:
- cancelled = False
- while aints:
- a = aints.pop()
- if a >= b:
- cancelled = True
- break
- popped = True
- if not cancelled:
- return S.Zero
- if aints or popped:
- # There are still non-positive numerator parameters.
- # This is a polynomial.
- return oo
- if len(self.ap) == len(self.bq) + 1:
- return S.One
- elif len(self.ap) <= len(self.bq):
- return oo
- else:
- return S.Zero
- @property
- def convergence_statement(self):
- """ Return a condition on z under which the series converges. """
- R = self.radius_of_convergence
- if R == 0:
- return False
- if R == oo:
- return True
- # The special functions and their approximations, page 44
- e = self.eta
- z = self.argument
- c1 = And(re(e) < 0, abs(z) <= 1)
- c2 = And(0 <= re(e), re(e) < 1, abs(z) <= 1, Ne(z, 1))
- c3 = And(re(e) >= 1, abs(z) < 1)
- return Or(c1, c2, c3)
- def _eval_simplify(self, **kwargs):
- from sympy.simplify.hyperexpand import hyperexpand
- return hyperexpand(self)
- class meijerg(TupleParametersBase):
- r"""
- The Meijer G-function is defined by a Mellin-Barnes type integral that
- resembles an inverse Mellin transform. It generalizes the hypergeometric
- functions.
- Explanation
- ===========
- The Meijer G-function depends on four sets of parameters. There are
- "*numerator parameters*"
- $a_1, \ldots, a_n$ and $a_{n+1}, \ldots, a_p$, and there are
- "*denominator parameters*"
- $b_1, \ldots, b_m$ and $b_{m+1}, \ldots, b_q$.
- Confusingly, it is traditionally denoted as follows (note the position
- of $m$, $n$, $p$, $q$, and how they relate to the lengths of the four
- parameter vectors):
- .. math ::
- G_{p,q}^{m,n} \left(\begin{matrix}a_1, \cdots, a_n & a_{n+1}, \cdots, a_p \\
- b_1, \cdots, b_m & b_{m+1}, \cdots, b_q
- \end{matrix} \middle| z \right).
- However, in SymPy the four parameter vectors are always available
- separately (see examples), so that there is no need to keep track of the
- decorating sub- and super-scripts on the G symbol.
- The G function is defined as the following integral:
- .. math ::
- \frac{1}{2 \pi i} \int_L \frac{\prod_{j=1}^m \Gamma(b_j - s)
- \prod_{j=1}^n \Gamma(1 - a_j + s)}{\prod_{j=m+1}^q \Gamma(1- b_j +s)
- \prod_{j=n+1}^p \Gamma(a_j - s)} z^s \mathrm{d}s,
- where $\Gamma(z)$ is the gamma function. There are three possible
- contours which we will not describe in detail here (see the references).
- If the integral converges along more than one of them, the definitions
- agree. The contours all separate the poles of $\Gamma(1-a_j+s)$
- from the poles of $\Gamma(b_k-s)$, so in particular the G function
- is undefined if $a_j - b_k \in \mathbb{Z}_{>0}$ for some
- $j \le n$ and $k \le m$.
- The conditions under which one of the contours yields a convergent integral
- are complicated and we do not state them here, see the references.
- Please note currently the Meijer G-function constructor does *not* check any
- convergence conditions.
- Examples
- ========
- You can pass the parameters either as four separate vectors:
- >>> from sympy import meijerg, Tuple, pprint
- >>> from sympy.abc import x, a
- >>> pprint(meijerg((1, 2), (a, 4), (5,), [], x), use_unicode=False)
- __1, 2 /1, 2 a, 4 | \
- /__ | | x|
- \_|4, 1 \ 5 | /
- Or as two nested vectors:
- >>> pprint(meijerg([(1, 2), (3, 4)], ([5], Tuple()), x), use_unicode=False)
- __1, 2 /1, 2 3, 4 | \
- /__ | | x|
- \_|4, 1 \ 5 | /
- As with the hypergeometric function, the parameters may be passed as
- arbitrary iterables. Vectors of length zero and one also have to be
- passed as iterables. The parameters need not be constants, but if they
- depend on the argument then not much implemented functionality should be
- expected.
- All the subvectors of parameters are available:
- >>> from sympy import pprint
- >>> g = meijerg([1], [2], [3], [4], x)
- >>> pprint(g, use_unicode=False)
- __1, 1 /1 2 | \
- /__ | | x|
- \_|2, 2 \3 4 | /
- >>> g.an
- (1,)
- >>> g.ap
- (1, 2)
- >>> g.aother
- (2,)
- >>> g.bm
- (3,)
- >>> g.bq
- (3, 4)
- >>> g.bother
- (4,)
- The Meijer G-function generalizes the hypergeometric functions.
- In some cases it can be expressed in terms of hypergeometric functions,
- using Slater's theorem. For example:
- >>> from sympy import hyperexpand
- >>> from sympy.abc import a, b, c
- >>> hyperexpand(meijerg([a], [], [c], [b], x), allow_hyper=True)
- x**c*gamma(-a + c + 1)*hyper((-a + c + 1,),
- (-b + c + 1,), -x)/gamma(-b + c + 1)
- Thus the Meijer G-function also subsumes many named functions as special
- cases. You can use ``expand_func()`` or ``hyperexpand()`` to (try to)
- rewrite a Meijer G-function in terms of named special functions. For
- example:
- >>> from sympy import expand_func, S
- >>> expand_func(meijerg([[],[]], [[0],[]], -x))
- exp(x)
- >>> hyperexpand(meijerg([[],[]], [[S(1)/2],[0]], (x/2)**2))
- sin(x)/sqrt(pi)
- See Also
- ========
- hyper
- sympy.simplify.hyperexpand
- References
- ==========
- .. [1] Luke, Y. L. (1969), The Special Functions and Their Approximations,
- Volume 1
- .. [2] https://en.wikipedia.org/wiki/Meijer_G-function
- """
- def __new__(cls, *args, **kwargs):
- if len(args) == 5:
- args = [(args[0], args[1]), (args[2], args[3]), args[4]]
- if len(args) != 3:
- raise TypeError("args must be either as, as', bs, bs', z or "
- "as, bs, z")
- def tr(p):
- if len(p) != 2:
- raise TypeError("wrong argument")
- return TupleArg(_prep_tuple(p[0]), _prep_tuple(p[1]))
- arg0, arg1 = tr(args[0]), tr(args[1])
- if Tuple(arg0, arg1).has(oo, zoo, -oo):
- raise ValueError("G-function parameters must be finite")
- if any((a - b).is_Integer and a - b > 0
- for a in arg0[0] for b in arg1[0]):
- raise ValueError("no parameter a1, ..., an may differ from "
- "any b1, ..., bm by a positive integer")
- # TODO should we check convergence conditions?
- return Function.__new__(cls, arg0, arg1, args[2], **kwargs)
- def fdiff(self, argindex=3):
- if argindex != 3:
- return self._diff_wrt_parameter(argindex[1])
- if len(self.an) >= 1:
- a = list(self.an)
- a[0] -= 1
- G = meijerg(a, self.aother, self.bm, self.bother, self.argument)
- return 1/self.argument * ((self.an[0] - 1)*self + G)
- elif len(self.bm) >= 1:
- b = list(self.bm)
- b[0] += 1
- G = meijerg(self.an, self.aother, b, self.bother, self.argument)
- return 1/self.argument * (self.bm[0]*self - G)
- else:
- return S.Zero
- def _diff_wrt_parameter(self, idx):
- # Differentiation wrt a parameter can only be done in very special
- # cases. In particular, if we want to differentiate with respect to
- # `a`, all other gamma factors have to reduce to rational functions.
- #
- # Let MT denote mellin transform. Suppose T(-s) is the gamma factor
- # appearing in the definition of G. Then
- #
- # MT(log(z)G(z)) = d/ds T(s) = d/da T(s) + ...
- #
- # Thus d/da G(z) = log(z)G(z) - ...
- # The ... can be evaluated as a G function under the above conditions,
- # the formula being most easily derived by using
- #
- # d Gamma(s + n) Gamma(s + n) / 1 1 1 \
- # -- ------------ = ------------ | - + ---- + ... + --------- |
- # ds Gamma(s) Gamma(s) \ s s + 1 s + n - 1 /
- #
- # which follows from the difference equation of the digamma function.
- # (There is a similar equation for -n instead of +n).
- # We first figure out how to pair the parameters.
- an = list(self.an)
- ap = list(self.aother)
- bm = list(self.bm)
- bq = list(self.bother)
- if idx < len(an):
- an.pop(idx)
- else:
- idx -= len(an)
- if idx < len(ap):
- ap.pop(idx)
- else:
- idx -= len(ap)
- if idx < len(bm):
- bm.pop(idx)
- else:
- bq.pop(idx - len(bm))
- pairs1 = []
- pairs2 = []
- for l1, l2, pairs in [(an, bq, pairs1), (ap, bm, pairs2)]:
- while l1:
- x = l1.pop()
- found = None
- for i, y in enumerate(l2):
- if not Mod((x - y).simplify(), 1):
- found = i
- break
- if found is None:
- raise NotImplementedError('Derivative not expressible '
- 'as G-function?')
- y = l2[i]
- l2.pop(i)
- pairs.append((x, y))
- # Now build the result.
- res = log(self.argument)*self
- for a, b in pairs1:
- sign = 1
- n = a - b
- base = b
- if n < 0:
- sign = -1
- n = b - a
- base = a
- for k in range(n):
- res -= sign*meijerg(self.an + (base + k + 1,), self.aother,
- self.bm, self.bother + (base + k + 0,),
- self.argument)
- for a, b in pairs2:
- sign = 1
- n = b - a
- base = a
- if n < 0:
- sign = -1
- n = a - b
- base = b
- for k in range(n):
- res -= sign*meijerg(self.an, self.aother + (base + k + 1,),
- self.bm + (base + k + 0,), self.bother,
- self.argument)
- return res
- def get_period(self):
- """
- Return a number $P$ such that $G(x*exp(I*P)) == G(x)$.
- Examples
- ========
- >>> from sympy import meijerg, pi, S
- >>> from sympy.abc import z
- >>> meijerg([1], [], [], [], z).get_period()
- 2*pi
- >>> meijerg([pi], [], [], [], z).get_period()
- oo
- >>> meijerg([1, 2], [], [], [], z).get_period()
- oo
- >>> meijerg([1,1], [2], [1, S(1)/2, S(1)/3], [1], z).get_period()
- 12*pi
- """
- # This follows from slater's theorem.
- def compute(l):
- # first check that no two differ by an integer
- for i, b in enumerate(l):
- if not b.is_Rational:
- return oo
- for j in range(i + 1, len(l)):
- if not Mod((b - l[j]).simplify(), 1):
- return oo
- return reduce(ilcm, (x.q for x in l), 1)
- beta = compute(self.bm)
- alpha = compute(self.an)
- p, q = len(self.ap), len(self.bq)
- if p == q:
- if oo in (alpha, beta):
- return oo
- return 2*pi*ilcm(alpha, beta)
- elif p < q:
- return 2*pi*beta
- else:
- return 2*pi*alpha
- def _eval_expand_func(self, **hints):
- from sympy.simplify.hyperexpand import hyperexpand
- return hyperexpand(self)
- def _eval_evalf(self, prec):
- # The default code is insufficient for polar arguments.
- # mpmath provides an optional argument "r", which evaluates
- # G(z**(1/r)). I am not sure what its intended use is, but we hijack it
- # here in the following way: to evaluate at a number z of |argument|
- # less than (say) n*pi, we put r=1/n, compute z' = root(z, n)
- # (carefully so as not to loose the branch information), and evaluate
- # G(z'**(1/r)) = G(z'**n) = G(z).
- from sympy.functions import exp_polar, ceiling
- import mpmath
- znum = self.argument._eval_evalf(prec)
- if znum.has(exp_polar):
- znum, branch = znum.as_coeff_mul(exp_polar)
- if len(branch) != 1:
- return
- branch = branch[0].args[0]/I
- else:
- branch = S.Zero
- n = ceiling(abs(branch/S.Pi)) + 1
- znum = znum**(S.One/n)*exp(I*branch / n)
- # Convert all args to mpf or mpc
- try:
- [z, r, ap, bq] = [arg._to_mpmath(prec)
- for arg in [znum, 1/n, self.args[0], self.args[1]]]
- except ValueError:
- return
- with mpmath.workprec(prec):
- v = mpmath.meijerg(ap, bq, z, r)
- return Expr._from_mpmath(v, prec)
- def _eval_as_leading_term(self, x, logx=None, cdir=0):
- from sympy.simplify.hyperexpand import hyperexpand
- return hyperexpand(self).as_leading_term(x, logx=logx, cdir=cdir)
- def integrand(self, s):
- """ Get the defining integrand D(s). """
- from sympy.functions.special.gamma_functions import gamma
- return self.argument**s \
- * Mul(*(gamma(b - s) for b in self.bm)) \
- * Mul(*(gamma(1 - a + s) for a in self.an)) \
- / Mul(*(gamma(1 - b + s) for b in self.bother)) \
- / Mul(*(gamma(a - s) for a in self.aother))
- @property
- def argument(self):
- """ Argument of the Meijer G-function. """
- return self.args[2]
- @property
- def an(self):
- """ First set of numerator parameters. """
- return Tuple(*self.args[0][0])
- @property
- def ap(self):
- """ Combined numerator parameters. """
- return Tuple(*(self.args[0][0] + self.args[0][1]))
- @property
- def aother(self):
- """ Second set of numerator parameters. """
- return Tuple(*self.args[0][1])
- @property
- def bm(self):
- """ First set of denominator parameters. """
- return Tuple(*self.args[1][0])
- @property
- def bq(self):
- """ Combined denominator parameters. """
- return Tuple(*(self.args[1][0] + self.args[1][1]))
- @property
- def bother(self):
- """ Second set of denominator parameters. """
- return Tuple(*self.args[1][1])
- @property
- def _diffargs(self):
- return self.ap + self.bq
- @property
- def nu(self):
- """ A quantity related to the convergence region of the integral,
- c.f. references. """
- return sum(self.bq) - sum(self.ap)
- @property
- def delta(self):
- """ A quantity related to the convergence region of the integral,
- c.f. references. """
- return len(self.bm) + len(self.an) - S(len(self.ap) + len(self.bq))/2
- @property
- def is_number(self):
- """ Returns true if expression has numeric data only. """
- return not self.free_symbols
- class HyperRep(Function):
- """
- A base class for "hyper representation functions".
- This is used exclusively in ``hyperexpand()``, but fits more logically here.
- pFq is branched at 1 if p == q+1. For use with slater-expansion, we want
- define an "analytic continuation" to all polar numbers, which is
- continuous on circles and on the ray t*exp_polar(I*pi). Moreover, we want
- a "nice" expression for the various cases.
- This base class contains the core logic, concrete derived classes only
- supply the actual functions.
- """
- @classmethod
- def eval(cls, *args):
- from sympy.functions.elementary.complexes import unpolarify
- newargs = tuple(map(unpolarify, args[:-1])) + args[-1:]
- if args != newargs:
- return cls(*newargs)
- @classmethod
- def _expr_small(cls, x):
- """ An expression for F(x) which holds for |x| < 1. """
- raise NotImplementedError
- @classmethod
- def _expr_small_minus(cls, x):
- """ An expression for F(-x) which holds for |x| < 1. """
- raise NotImplementedError
- @classmethod
- def _expr_big(cls, x, n):
- """ An expression for F(exp_polar(2*I*pi*n)*x), |x| > 1. """
- raise NotImplementedError
- @classmethod
- def _expr_big_minus(cls, x, n):
- """ An expression for F(exp_polar(2*I*pi*n + pi*I)*x), |x| > 1. """
- raise NotImplementedError
- def _eval_rewrite_as_nonrep(self, *args, **kwargs):
- from sympy.functions.elementary.piecewise import Piecewise
- x, n = self.args[-1].extract_branch_factor(allow_half=True)
- minus = False
- newargs = self.args[:-1] + (x,)
- if not n.is_Integer:
- minus = True
- n -= S.Half
- newerargs = newargs + (n,)
- if minus:
- small = self._expr_small_minus(*newargs)
- big = self._expr_big_minus(*newerargs)
- else:
- small = self._expr_small(*newargs)
- big = self._expr_big(*newerargs)
- if big == small:
- return small
- return Piecewise((big, abs(x) > 1), (small, True))
- def _eval_rewrite_as_nonrepsmall(self, *args, **kwargs):
- x, n = self.args[-1].extract_branch_factor(allow_half=True)
- args = self.args[:-1] + (x,)
- if not n.is_Integer:
- return self._expr_small_minus(*args)
- return self._expr_small(*args)
- class HyperRep_power1(HyperRep):
- """ Return a representative for hyper([-a], [], z) == (1 - z)**a. """
- @classmethod
- def _expr_small(cls, a, x):
- return (1 - x)**a
- @classmethod
- def _expr_small_minus(cls, a, x):
- return (1 + x)**a
- @classmethod
- def _expr_big(cls, a, x, n):
- if a.is_integer:
- return cls._expr_small(a, x)
- return (x - 1)**a*exp((2*n - 1)*pi*I*a)
- @classmethod
- def _expr_big_minus(cls, a, x, n):
- if a.is_integer:
- return cls._expr_small_minus(a, x)
- return (1 + x)**a*exp(2*n*pi*I*a)
- class HyperRep_power2(HyperRep):
- """ Return a representative for hyper([a, a - 1/2], [2*a], z). """
- @classmethod
- def _expr_small(cls, a, x):
- return 2**(2*a - 1)*(1 + sqrt(1 - x))**(1 - 2*a)
- @classmethod
- def _expr_small_minus(cls, a, x):
- return 2**(2*a - 1)*(1 + sqrt(1 + x))**(1 - 2*a)
- @classmethod
- def _expr_big(cls, a, x, n):
- sgn = -1
- if n.is_odd:
- sgn = 1
- n -= 1
- return 2**(2*a - 1)*(1 + sgn*I*sqrt(x - 1))**(1 - 2*a) \
- *exp(-2*n*pi*I*a)
- @classmethod
- def _expr_big_minus(cls, a, x, n):
- sgn = 1
- if n.is_odd:
- sgn = -1
- return sgn*2**(2*a - 1)*(sqrt(1 + x) + sgn)**(1 - 2*a)*exp(-2*pi*I*a*n)
- class HyperRep_log1(HyperRep):
- """ Represent -z*hyper([1, 1], [2], z) == log(1 - z). """
- @classmethod
- def _expr_small(cls, x):
- return log(1 - x)
- @classmethod
- def _expr_small_minus(cls, x):
- return log(1 + x)
- @classmethod
- def _expr_big(cls, x, n):
- return log(x - 1) + (2*n - 1)*pi*I
- @classmethod
- def _expr_big_minus(cls, x, n):
- return log(1 + x) + 2*n*pi*I
- class HyperRep_atanh(HyperRep):
- """ Represent hyper([1/2, 1], [3/2], z) == atanh(sqrt(z))/sqrt(z). """
- @classmethod
- def _expr_small(cls, x):
- return atanh(sqrt(x))/sqrt(x)
- def _expr_small_minus(cls, x):
- return atan(sqrt(x))/sqrt(x)
- def _expr_big(cls, x, n):
- if n.is_even:
- return (acoth(sqrt(x)) + I*pi/2)/sqrt(x)
- else:
- return (acoth(sqrt(x)) - I*pi/2)/sqrt(x)
- def _expr_big_minus(cls, x, n):
- if n.is_even:
- return atan(sqrt(x))/sqrt(x)
- else:
- return (atan(sqrt(x)) - pi)/sqrt(x)
- class HyperRep_asin1(HyperRep):
- """ Represent hyper([1/2, 1/2], [3/2], z) == asin(sqrt(z))/sqrt(z). """
- @classmethod
- def _expr_small(cls, z):
- return asin(sqrt(z))/sqrt(z)
- @classmethod
- def _expr_small_minus(cls, z):
- return asinh(sqrt(z))/sqrt(z)
- @classmethod
- def _expr_big(cls, z, n):
- return S.NegativeOne**n*((S.Half - n)*pi/sqrt(z) + I*acosh(sqrt(z))/sqrt(z))
- @classmethod
- def _expr_big_minus(cls, z, n):
- return S.NegativeOne**n*(asinh(sqrt(z))/sqrt(z) + n*pi*I/sqrt(z))
- class HyperRep_asin2(HyperRep):
- """ Represent hyper([1, 1], [3/2], z) == asin(sqrt(z))/sqrt(z)/sqrt(1-z). """
- # TODO this can be nicer
- @classmethod
- def _expr_small(cls, z):
- return HyperRep_asin1._expr_small(z) \
- /HyperRep_power1._expr_small(S.Half, z)
- @classmethod
- def _expr_small_minus(cls, z):
- return HyperRep_asin1._expr_small_minus(z) \
- /HyperRep_power1._expr_small_minus(S.Half, z)
- @classmethod
- def _expr_big(cls, z, n):
- return HyperRep_asin1._expr_big(z, n) \
- /HyperRep_power1._expr_big(S.Half, z, n)
- @classmethod
- def _expr_big_minus(cls, z, n):
- return HyperRep_asin1._expr_big_minus(z, n) \
- /HyperRep_power1._expr_big_minus(S.Half, z, n)
- class HyperRep_sqrts1(HyperRep):
- """ Return a representative for hyper([-a, 1/2 - a], [1/2], z). """
- @classmethod
- def _expr_small(cls, a, z):
- return ((1 - sqrt(z))**(2*a) + (1 + sqrt(z))**(2*a))/2
- @classmethod
- def _expr_small_minus(cls, a, z):
- return (1 + z)**a*cos(2*a*atan(sqrt(z)))
- @classmethod
- def _expr_big(cls, a, z, n):
- if n.is_even:
- return ((sqrt(z) + 1)**(2*a)*exp(2*pi*I*n*a) +
- (sqrt(z) - 1)**(2*a)*exp(2*pi*I*(n - 1)*a))/2
- else:
- n -= 1
- return ((sqrt(z) - 1)**(2*a)*exp(2*pi*I*a*(n + 1)) +
- (sqrt(z) + 1)**(2*a)*exp(2*pi*I*a*n))/2
- @classmethod
- def _expr_big_minus(cls, a, z, n):
- if n.is_even:
- return (1 + z)**a*exp(2*pi*I*n*a)*cos(2*a*atan(sqrt(z)))
- else:
- return (1 + z)**a*exp(2*pi*I*n*a)*cos(2*a*atan(sqrt(z)) - 2*pi*a)
- class HyperRep_sqrts2(HyperRep):
- """ Return a representative for
- sqrt(z)/2*[(1-sqrt(z))**2a - (1 + sqrt(z))**2a]
- == -2*z/(2*a+1) d/dz hyper([-a - 1/2, -a], [1/2], z)"""
- @classmethod
- def _expr_small(cls, a, z):
- return sqrt(z)*((1 - sqrt(z))**(2*a) - (1 + sqrt(z))**(2*a))/2
- @classmethod
- def _expr_small_minus(cls, a, z):
- return sqrt(z)*(1 + z)**a*sin(2*a*atan(sqrt(z)))
- @classmethod
- def _expr_big(cls, a, z, n):
- if n.is_even:
- return sqrt(z)/2*((sqrt(z) - 1)**(2*a)*exp(2*pi*I*a*(n - 1)) -
- (sqrt(z) + 1)**(2*a)*exp(2*pi*I*a*n))
- else:
- n -= 1
- return sqrt(z)/2*((sqrt(z) - 1)**(2*a)*exp(2*pi*I*a*(n + 1)) -
- (sqrt(z) + 1)**(2*a)*exp(2*pi*I*a*n))
- def _expr_big_minus(cls, a, z, n):
- if n.is_even:
- return (1 + z)**a*exp(2*pi*I*n*a)*sqrt(z)*sin(2*a*atan(sqrt(z)))
- else:
- return (1 + z)**a*exp(2*pi*I*n*a)*sqrt(z) \
- *sin(2*a*atan(sqrt(z)) - 2*pi*a)
- class HyperRep_log2(HyperRep):
- """ Represent log(1/2 + sqrt(1 - z)/2) == -z/4*hyper([3/2, 1, 1], [2, 2], z) """
- @classmethod
- def _expr_small(cls, z):
- return log(S.Half + sqrt(1 - z)/2)
- @classmethod
- def _expr_small_minus(cls, z):
- return log(S.Half + sqrt(1 + z)/2)
- @classmethod
- def _expr_big(cls, z, n):
- if n.is_even:
- return (n - S.Half)*pi*I + log(sqrt(z)/2) + I*asin(1/sqrt(z))
- else:
- return (n - S.Half)*pi*I + log(sqrt(z)/2) - I*asin(1/sqrt(z))
- def _expr_big_minus(cls, z, n):
- if n.is_even:
- return pi*I*n + log(S.Half + sqrt(1 + z)/2)
- else:
- return pi*I*n + log(sqrt(1 + z)/2 - S.Half)
- class HyperRep_cosasin(HyperRep):
- """ Represent hyper([a, -a], [1/2], z) == cos(2*a*asin(sqrt(z))). """
- # Note there are many alternative expressions, e.g. as powers of a sum of
- # square roots.
- @classmethod
- def _expr_small(cls, a, z):
- return cos(2*a*asin(sqrt(z)))
- @classmethod
- def _expr_small_minus(cls, a, z):
- return cosh(2*a*asinh(sqrt(z)))
- @classmethod
- def _expr_big(cls, a, z, n):
- return cosh(2*a*acosh(sqrt(z)) + a*pi*I*(2*n - 1))
- @classmethod
- def _expr_big_minus(cls, a, z, n):
- return cosh(2*a*asinh(sqrt(z)) + 2*a*pi*I*n)
- class HyperRep_sinasin(HyperRep):
- """ Represent 2*a*z*hyper([1 - a, 1 + a], [3/2], z)
- == sqrt(z)/sqrt(1-z)*sin(2*a*asin(sqrt(z))) """
- @classmethod
- def _expr_small(cls, a, z):
- return sqrt(z)/sqrt(1 - z)*sin(2*a*asin(sqrt(z)))
- @classmethod
- def _expr_small_minus(cls, a, z):
- return -sqrt(z)/sqrt(1 + z)*sinh(2*a*asinh(sqrt(z)))
- @classmethod
- def _expr_big(cls, a, z, n):
- return -1/sqrt(1 - 1/z)*sinh(2*a*acosh(sqrt(z)) + a*pi*I*(2*n - 1))
- @classmethod
- def _expr_big_minus(cls, a, z, n):
- return -1/sqrt(1 + 1/z)*sinh(2*a*asinh(sqrt(z)) + 2*a*pi*I*n)
- class appellf1(Function):
- r"""
- This is the Appell hypergeometric function of two variables as:
- .. math ::
- F_1(a,b_1,b_2,c,x,y) = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty}
- \frac{(a)_{m+n} (b_1)_m (b_2)_n}{(c)_{m+n}}
- \frac{x^m y^n}{m! n!}.
- Examples
- ========
- >>> from sympy import appellf1, symbols
- >>> x, y, a, b1, b2, c = symbols('x y a b1 b2 c')
- >>> appellf1(2., 1., 6., 4., 5., 6.)
- 0.0063339426292673
- >>> appellf1(12., 12., 6., 4., 0.5, 0.12)
- 172870711.659936
- >>> appellf1(40, 2, 6, 4, 15, 60)
- appellf1(40, 2, 6, 4, 15, 60)
- >>> appellf1(20., 12., 10., 3., 0.5, 0.12)
- 15605338197184.4
- >>> appellf1(40, 2, 6, 4, x, y)
- appellf1(40, 2, 6, 4, x, y)
- >>> appellf1(a, b1, b2, c, x, y)
- appellf1(a, b1, b2, c, x, y)
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Appell_series
- .. [2] http://functions.wolfram.com/HypergeometricFunctions/AppellF1/
- """
- @classmethod
- def eval(cls, a, b1, b2, c, x, y):
- if default_sort_key(b1) > default_sort_key(b2):
- b1, b2 = b2, b1
- x, y = y, x
- return cls(a, b1, b2, c, x, y)
- elif b1 == b2 and default_sort_key(x) > default_sort_key(y):
- x, y = y, x
- return cls(a, b1, b2, c, x, y)
- if x == 0 and y == 0:
- return S.One
- def fdiff(self, argindex=5):
- a, b1, b2, c, x, y = self.args
- if argindex == 5:
- return (a*b1/c)*appellf1(a + 1, b1 + 1, b2, c + 1, x, y)
- elif argindex == 6:
- return (a*b2/c)*appellf1(a + 1, b1, b2 + 1, c + 1, x, y)
- elif argindex in (1, 2, 3, 4):
- return Derivative(self, self.args[argindex-1])
- else:
- raise ArgumentIndexError(self, argindex)
|