<jupyter_start><jupyter_text>Introduction to Meijer $G$-functionsThis notebook provides introductory material on the Meijer-$G$ functions based on our paper *"Demystifying Black-box Models with Symbolic Metamodels"* submitted to **NeurIPS 2019** by *<NAME> and <NAME>*. The notebook provides the formal definition of Meijer-$G$ functions, along with examples for how to use our Python implementation of the $G$-function in the context of symbolic computation.  What are Meijer $G$-functions? The **Meijer G-functions** are a family of univariate functions, each of them determined by finitely many indexes. The $G$-function is a linear combination of certain special functions of standard type, and hence it is often use to solve indefinite integrals [1]. Despite their useful properties, $G$-functions are not very well known in the mathematical community. In this notebook, we provide a brief background on Meijer $G$-functions, presenting its formal definition along with its basic properties. In addition, we show how to use our python implementation of $G$-functions in symbolic computations.  Formally, the Meijer $G$-function is a univariate function given by a line integral in the complex plane [2, 3]: $G^{m,n}_{p, q}\left(^{a_1,\ldots,a_p}_{b_1,\ldots,b_q}\,\Big|\, x\right) = \frac{1}{2\pi i} \int_{\mathcal{L}} \frac{\prod^m_{j=1} \Gamma(b_j - s)\prod^n_{j=1} \Gamma(1 - a_j + s)}{\prod^q_{j=m+1} \Gamma(1 - b_j + s)\prod^p_{j=n+1} \Gamma(a_j + s)}\, x^s\, ds$ where $\Gamma(.)$ is the Gamma function, $0 \leq m \leq q$, $0 \leq n \leq p$, where $m$, $n$, $p$ and $q$ are integer numbers, $a_k - b_j \neq 1, 2, 3,.\,.\,.$ for $k = 1, 2,\,.\,.\,., n$, $j = 1, 2\,.\,.\,., m$ and $x \neq 0$. This means that no pole of any $\Gamma(b_j - s)$ coincides with any pole of any $\Gamma(1 - a_k + s)$. The integration path $\mathcal{L}$ separates the poles of the factors $\Gamma(b_j - s)$ from those of the factors $\Gamma(1 - a_k + s)$.  As shown in the Figure above, there are three cases in which integration path $\mathcal{L}$ can be chosen to separate the poles and the zeros as follows [4]: - $L$ goes from $-i\infty$ to $i\infty$.- $L$ is a loop that starts at $i\infty$ on a line parallel to the $+$ve real axis, encircles the poles of the factors $\Gamma(b_j - s)$ and returns to $i\infty$ on another line parallel to the $+$ve real axis.- $L$ is a loop that starts at $i\infty$ on a line parallel to the $-$ve real axis, encircles the poles of the factors $\Gamma(1 - a_k + s)$ and returns to $\infty$ on another line parallel to the $-$ve real axis.When more than one of Cases 1, 2, and 3 is applicable the same value is obtained for the $G$-function. The conditions for convergence of the line integral can be established by applying Stirling's asymptotic approximation to the gamma functions $\Gamma(b_j - s)$ and $\Gamma(1 - a_k + s)$  in the integrand. As a consequence of the definition above, the Meijer $G$-function is an analytic function of $x$ (with possible exception of the origin $x$ = 0 and the unit circle $|x| = 1$).  How to use Meijer $G$-functions in PySymbolic? **PySymbolic** is a symbolic computation library that we developed in order to implement symbolic metamodeling. This is basically a wrapper over **SymPy** that provides all the relevant functionalities needed for metamodeling, in addition to a high-level API that makes symbolic comptations easier. To see how can we evaluate Meijer $G$-functions in PySymbolic, we first import the **MeijerG** class from the **models.special_functions** module as follows:<jupyter_code>from pysymbolic.models.special_functions import MeijerG<jupyter_output><empty_output><jupyter_text>In order to create an instance of a Meijer $G$-function, we only need to provide it with the poles and the zeros through two input arguments as follows:    **MeijerG(theta=[a_1,..., a_n,..., a_p, b_1,...,b_m,...,b_q, scale], order=[m, n, p, q])**Here the arguments **theta** and **order** correspond to the actual values of the poles and zeros, and the indexes $m$, $n$, $p$ and $q$, respectively. The **scale** variable corresponds to a real number by which the input $x$ is scaled. To demonstrate the above, let us create an object holding the following Meijer $G$-function: $G^{0,1}_{3, 1}\left(^{2, 2, 2}_{1}\,\Big|\, 5x\right)$ To create the corresponding MeijerG object, we define the inputs as theta = [2,2,2,1,5] since $a_1=a_2=a_3 = 2$, and $b_1=1$, whereas the scaling factor is 5. Thus, we have the following:<jupyter_code>MeijerG(theta=[2, 2, 2, 1, 5], order=[0, 1, 3, 1])<jupyter_output><empty_output><jupyter_text>Now to recover the expression above in a symbolic format, we use the "expression" method:<jupyter_code>My_meijerg_object = MeijerG(theta=[2, 2, 2, 1, 5], order=[0, 1, 3, 1])

My_meijerg_object.expression()<jupyter_output><empty_output><jupyter_text>As we can see above, it turns out that this particular selection of poles and zeros reduces the Meijer $G$-function to an identity function! This is the power of Meijer $G$-functions: for different selections of poles and zeros, they reduce to familiar functional forms. In addition to the **theta** and **order** inputs, the **MeijerG** object takes two other optional arguments:- **evaluation_mode**: When evaluating the value numerical value of a Meijer $G$-function at a specific value of $x$, this argument determines the way numerical evaluation is carried out. The options include ['eval','numpy', 'theano', 'cython']. The 'eval' option applies direct symbolic computation, which is the slowest approach. 'numpy' and 'theano' apply numeric evaluation using numpy arrays or theano graphs, whereas 'cython' creates an intermediate C code to enable fast computation of the Meijer $G$-function. The default setting is 'numpy'.  - **approximation_order**: When approximating the Meijer $G$-function for intermediate computation, we use a Taylor series approximation. This argument determines the number of terms in a Taylor expansion to include in the approximation, and is set to a default value of 15. As mentioned earlier, the main strength of the Meijer $G$-function is that they reduce to familiar functional forms for different selections of poles and zeros. Notable examples include the following:- $G^{0,1}_{3, 1}\left(^{2, 2, 2}_{1}\,\Big|\, x\right) = x$ (which we have demonstrated above).- $G^{1,0}_{0, 1}\left(^{-}_{0}\,\Big|\, x\right) = e^{-x}$.- $G^{1,2}_{2, 2}\left(^{1,1}_{1,0}\,\Big|\, x\right) = \log(x+1)$.- $G^{1,2}_{2, 2}\left(^{1,1}_{1,1}\,\Big|\, x\right) = \frac{x}{(x+1)}$. In what follows, we show how can we recover all the identities above using the MeijerG object. To enable printing in LaTex format inside the notebook, we use the "init_print" command in sympy.<jupyter_code>from sympy import *
init_printing()
identity_function    = MeijerG(theta=[2, 2, 2, 1, 1], order=[0, 1, 3, 1])
exponential_function = MeijerG(theta=[0, 1], order=[1, 0, 0, 1])
logarithmic_function = MeijerG(theta=[1,1,1,0,1], order=[1, 2, 2, 2])
rational_function    = MeijerG(theta=[1,1,1,1,1], order=[1, 2, 2, 2])<jupyter_output><empty_output><jupyter_text>Now let us check if the magic works!<jupyter_code>identity_function.expression()
exponential_function.expression()
logarithmic_function.expression()
rational_function.expression()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1263833857084301, 'fraction_numerical': 0.01967481896433939, 'mean_word_length': 4.879518072289157, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '177870', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2319, 'n_tokens_neox': 2161, 'n_words': 994, 'path': 'alg/symbolic_metamodeling/1-_Introduction_to_Meijer_G-functions.ipynb', 'repo_name': 'migelekarapet/mlforhealthlabpub', 'size': '7342'}