Multiplicatively Convolutional Fast Integral Transforms
=======================================================


Features
--------

* Compute integral transforms
* Inverse transform without analytic inversion
* Integral kernels as derivatives
* Transform input array along any axis
* Output the matrix form
* 1-to-n transform for multiple kernels (TODO)
* Easily extensible for other kernels


Algorithm
---------

`mcfit` computes integral transforms of the form

.. math:: G(y) = \int_0^\infty F(x) K(xy) \frac{dx}x

where :math:`F(x)` is the input function, :math:`G(y)` is the output function,
and :math:`K(xy)` is the integral kernel.
One is free to scale all three functions by a power law

.. math:: g(y) = \int_0^\infty f(x) k(xy) \frac{dx}x

where :math:`f(x)=x^{-q}F(x)`, :math:`g(y)=y^q G(y)`, and :math:`k(t)=t^q K(t)`.
And :math:`q` is a tilt parameter serving to shift power of :math:`x` between
the input function and the kernel.

`mcfit` implements the FFTLog algorithm.
The idea is to take advantage of the convolution theorem in :math:`\ln x` and
:math:`\ln y`.
It approximates the input function with truncated Fourier series over one
period of a periodic approximant, and use the exact Fourier transform of the
kernel.
One can calculate the latter analytically as a Mellin transform.
This algorithm is optimal when the input function is smooth in :math:`\ln x`,
and is ideal for oscillatory kernels with input spanning a wide range in
:math:`\ln x`.


Examples
--------

One can perform the following pair of Hankel transforms

.. math::

    e^{-y} &= \int_0^\infty (1+x^2)^{-\frac32} J_0(xy) x dx \\
    (1+y^2)^{-\frac32} &= \int_0^\infty e^{-y} J_0(xy) x dx

easily as follows

.. code-block:: python

    def F_fun(x): return 1 / (1 + x*x)**1.5
    def G_fun(y): return numpy.exp(-y)

    from mcfit import Hankel

    x = numpy.logspace(-3, 3, num=60, endpoint=False)
    F = F_fun(x)
    H = Hankel(x, lowring=True)
    y, G = H(F, extrap=True)
    numpy.allclose(G, G_fun(y), rtol=1e-8, atol=1e-8)

    y = numpy.logspace(-4, 2, num=60, endpoint=False)
    G = G_fun(y)
    H_inv = Hankel(y, lowring=True)
    x, F = H_inv(G, extrap=True)
    numpy.allclose(F, F_fun(x), rtol=1e-10, atol=1e-10)

Cosmologists often need to transform a power spectrum to its correlation
function

.. code-block:: python

    from mcfit import P2xi
    k, P = numpy.loadtxt('P.txt', unpack=True)
    r, xi = P2xi(k)(P)

and the other way around

.. code-block:: python

    from mcfit import xi2P
    r, xi = numpy.loadtxt('xi.txt', unpack=True)
    k, P = xi2P(r)(xi)

Similarly for the quadrupoles

.. code-block:: python

    k, P2 = numpy.loadtxt('P2.txt', unpack=True)
    r, xi2 = P2xi(k, l=2)(P2)

Also useful to the cosmologists is the tool below that computes the variance of
the overdensity field as a function of radius, from which :math:`\sigma_8` can
be interpolated.

.. code-block:: python

    from mcfit import TophatVar
    R, var = TophatVar(k, lowring=True)(P, extrap=True)
    from scipy.interpolate import CubicSpline
    varR = CubicSpline(R, var)
    sigma8 = numpy.sqrt(varR(8))
