| | """Hypergeometric and Meijer G-functions""" |
| | from collections import Counter |
| |
|
| | from sympy.core import S, Mod |
| | from sympy.core.add import Add |
| | from sympy.core.expr import Expr |
| | from sympy.core.function import DefinedFunction, Derivative, ArgumentIndexError |
| |
|
| | from sympy.core.containers import Tuple |
| | from sympy.core.mul import Mul |
| | from sympy.core.numbers import I, pi, oo, zoo |
| | from sympy.core.parameters import global_parameters |
| | from sympy.core.relational import Ne |
| | from sympy.core.sorting import default_sort_key |
| | from sympy.core.symbol import Dummy |
| |
|
| | from sympy.external.gmpy import lcm |
| | from sympy.functions import (sqrt, exp, log, sin, cos, asin, atan, |
| | sinh, cosh, asinh, acosh, atanh, acoth) |
| | from sympy.functions import factorial, RisingFactorial |
| | from sympy.functions.elementary.complexes import Abs, re, unpolarify |
| | from sympy.functions.elementary.exponential import exp_polar |
| | from sympy.functions.elementary.integers import ceiling |
| | from sympy.functions.elementary.piecewise import Piecewise |
| | from sympy.logic.boolalg import (And, Or) |
| | from sympy import ordered |
| |
|
| |
|
| | class TupleArg(Tuple): |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | def as_leading_term(self, *x, logx=None, cdir=0): |
| | return TupleArg(*[f.as_leading_term(*x, logx=logx, cdir=cdir) for f in self.args]) |
| |
|
| | 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]) |
| |
|
| |
|
| | |
| | |
| |
|
| |
|
| | 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) |
| | |
| | """ |
| | return TupleArg(*[unpolarify(x) for x in v]) |
| |
|
| |
|
| | class TupleParametersBase(DefinedFunction): |
| | """ Base class that takes care of differentiation, when some of |
| | the arguments are actually tuples. """ |
| | |
| | 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 |
| | >>> h = hyper((1, 2, 3), [3, 4], x); h |
| | hyper((1, 2), (4,), x) |
| | >>> hyper((3, 1, 2), [3, 4], x, evaluate=False) # don't remove duplicates |
| | hyper((1, 2, 3), (3, 4), x) |
| | |
| | There is also pretty printing (it looks better using Unicode): |
| | |
| | >>> from sympy import pprint |
| | >>> pprint(h, use_unicode=False) |
| | _ |
| | |_ /1, 2 | \ |
| | | | | x| |
| | 2 1 \ 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((a, n), (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): |
| | |
| | if kwargs.pop('evaluate', global_parameters.evaluate): |
| | ca = Counter(Tuple(*ap)) |
| | cb = Counter(Tuple(*bq)) |
| | common = ca & cb |
| | arg = ap, bq = [], [] |
| | for i, c in enumerate((ca, cb)): |
| | c -= common |
| | for k in ordered(c): |
| | arg[i].extend([k]*c[k]) |
| | else: |
| | ap = list(ordered(ap)) |
| | bq = list(ordered(bq)) |
| | return super().__new__(cls, _prep_tuple(ap), _prep_tuple(bq), z, **kwargs) |
| |
|
| | @classmethod |
| | def eval(cls, ap, bq, z): |
| | 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.concrete.summations import Sum |
| | n = Dummy("n", integer=True) |
| | rfap = [RisingFactorial(a, n) for a in ap] |
| | rfbq = [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, cdir): |
| | 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 not (arg == x and x0 == 0): |
| | |
| | |
| | |
| | from sympy.simplify.hyperexpand import hyperexpand |
| | return hyperexpand(super()._eval_nseries(x, n, logx)) |
| |
|
| | terms = [] |
| |
|
| | for i in range(n): |
| | num = Mul(*[RisingFactorial(a, i) for a in ap]) |
| | den = Mul(*[RisingFactorial(b, i) for b in bq]) |
| | 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: |
| | |
| | |
| | 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 |
| | |
| | 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 4, a | \ |
| | /__ | | 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") |
| | p = [list(ordered(i)) for i in p] |
| | 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") |
| |
|
| | |
| | return super().__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): |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | 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)) |
| |
|
| | |
| | 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 |
| | |
| | """ |
| | |
| | def compute(l): |
| | |
| | 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 lcm(*(x.q for x in l)) |
| | 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*lcm(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): |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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/pi)) + 1 |
| | znum = znum**(S.One/n)*exp(I*branch / n) |
| |
|
| | |
| | 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, cdir): |
| | 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(DefinedFunction): |
| | """ |
| | 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): |
| | 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): |
| | 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). """ |
| | |
| | @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))). """ |
| | |
| | |
| |
|
| | @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(DefinedFunction): |
| | 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] https://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) |
| |
|