from __future__ import absolute_import, division, print_function
import warnings
import numpy as np
from numpy import pi, sqrt, ones, zeros
from scipy import integrate as intg
import scipy.special.orthogonal as ort
from scipy import special as sp

from scipy.integrate import simps, trapz
from wafo.plotbackend import plotbackend as plt
from wafo.demos import humps
from wafo.misc import dea3
# from wafo.dctpack import dct
from collections import defaultdict
# from pychebfun import Chebfun

_EPS = np.finfo(float).eps
_NODES_AND_WEIGHTS = defaultdict(list)

__all__ = ['dea3', 'clencurt', 'romberg',
           'h_roots', 'j_roots', 'la_roots', 'p_roots', 'qrule',
           'gaussq', 'richardson', 'quadgr', 'qdemo']


def _assert(cond, msg):
    if not cond:
        raise ValueError(msg)


def _assert_warn(cond, msg):
    if not cond:
        warnings.warn(msg)


def clencurt(fun, a, b, n=5, trace=False):
    """
    Numerical evaluation of an integral, Clenshaw-Curtis method.

    Parameters
    ----------
    fun : callable
    a, b : array-like
        Lower and upper integration limit, respectively.
    n : integer
        defines number of evaluation points (default 5)

    Returns
    -------
    q_val     = evaluated integral
    tol   = Estimate of the approximation error

    Notes
    -----
    CLENCURT approximates the integral of f(x) from a to b
    using an 2*n+1 points Clenshaw-Curtis formula.
    The error estimate is usually a conservative estimate of the
    approximation error.
    The integral is exact for polynomials of degree 2*n or less.

    Example
    -------
    >>> import numpy as np
    >>> val, err = clencurt(np.exp, 0, 2)
    >>> np.allclose(val, np.expm1(2)), err[0] < 1e-10
    (True, True)


    See also
    --------
    simpson,
    gaussq

    References
    ----------
    [1] Goodwin, E.T. (1961),
    "Modern Computing Methods",
    2nd edition, New yourk: Philosophical Library, pp. 78--79

    [2] Clenshaw, C.W. and Curtis, A.R. (1960),
    Numerische Matematik, Vol. 2, pp. 197--205
    """
    # make sure n_2 is even
    n_2 = 2 * int(n)
    a, b = np.atleast_1d(a, b)
    a_shape = a.shape
    a = a.ravel()
    b = b.ravel()

    a_size = np.prod(a_shape)

    s = np.c_[0:n_2 + 1:1]
    s_2 = np.c_[0:n_2 + 1:2]
    x = np.cos(np.pi * s / n_2) * (b - a) / 2. + (b + a) / 2

    if hasattr(fun, '__call__'):
        f = fun(x)
    else:
        x_0 = np.flipud(fun[:, 0])
        n_2 = len(x_0) - 1
        _assert(abs(x - x_0) <= 1e-8,
                'Input vector x must equal cos(pi*s/n_2)*(b-a)/2+(b+a)/2')

        f = np.flipud(fun[:, 1::])

    if trace:
        plt.plot(x, f, '+')

    # using a Gauss-Lobatto variant, i.e., first and last
    # term f(a) and f(b) is multiplied with 0.5
    f[0, :] = f[0, :] / 2
    f[n_2, :] = f[n_2, :] / 2

    # x = cos(pi*0:n_2/n_2)
    # f = f(x)
    #
    #               N+1
    #  c(k) = (2/N) sum  f''(n)*cos(pi*(2*k-2)*(n-1)/N), 1 <= k <= N/2+1.
    #               n=1
    n = n_2 // 2
    fft = np.fft.fft
    tmp = np.real(fft(f[:n_2, :], axis=0))
    c = 2 / n_2 * (tmp[0:n + 1, :] + np.cos(np.pi * s_2) * f[n_2, :])
    c[0, :] = c[0, :] / 2
    c[n, :] = c[n, :] / 2

    c = c[0:n + 1, :] / ((s_2 - 1) * (s_2 + 1))
    q_val = (a - b) * np.sum(c, axis=0)

    abserr = (b - a) * np.abs(c[n, :])

    if a_size > 1:
        abserr = np.reshape(abserr, a_shape)
        q_val = np.reshape(q_val, a_shape)
    return q_val, abserr


def romberg(fun, a, b, releps=1e-3, abseps=1e-3):
    """
    Numerical integration with the Romberg method

    Parameters
    ----------
    fun : callable
        function to integrate
    a, b : real scalars
        lower and upper integration limits,  respectively.
    releps, abseps : scalar, optional
        requested relative and absolute error, respectively.

    Returns
    -------
    Q : scalar
        value of integral
    abserr : scalar
        estimated absolute error of integral

    ROMBERG approximates the integral of F(X) from A to B
    using Romberg's method of integration.  The function F
    must return a vector of output values if a vector of input values is given.


    Example
    -------
    >>> import numpy as np
    >>> [q,err] = romberg(np.sqrt,0,10,0,1e-4)
    >>> np.allclose(q, 21.08185107)
    True
    >>> err[0] < 1e-4
    True
    """
    h = b - a
    h_min = 1.0e-9
    # Max size of extrapolation table
    table_limit = max(min(np.round(np.log2(h / h_min)), 30), 3)

    rom = zeros((2, table_limit))

    rom[0, 0] = h * (fun(a) + fun(b)) / 2
    ipower = 1
    f_p = ones(table_limit) * 4

    # q_val1 = 0
    q_val2 = 0.
    q_val4 = rom[0, 0]
    abserr = q_val4
    # epstab = zeros(1,decdigs+7)
    # newflg = 1
    # [res,abserr,epstab,newflg] = dea(newflg,q_val4,abserr,epstab)
    two = 1
    one = 0
    converged = False
    for i in range(1, table_limit):
        h *= 0.5
        u_n5 = np.sum(fun(a + np.arange(1, 2 * ipower, 2) * h)) * h

        #     trapezoidal approximations
        # T2n = 0.5 * (Tn + Un) = 0.5*Tn + u_n5
        rom[two, 0] = 0.5 * rom[one, 0] + u_n5

        f_p[i] = 4 * f_p[i - 1]
        #   Richardson extrapolation
        for k in range(i):
            rom[two, k + 1] = (rom[two, k] +
                               (rom[two, k] - rom[one, k]) / (f_p[k] - 1))

        q_val1 = q_val2
        q_val2 = q_val4
        q_val4 = rom[two, i]

        if 2 <= i:
            res, abserr = dea3(q_val1, q_val2, q_val4)
            # q_val4 = res
            converged = abserr <= max(abseps, releps * abs(res))
            if converged:
                break

        # rom(1,1:i) = rom(2,1:i)
        two = one
        one = (one + 1) % 2
        ipower *= 2
    _assert(converged, "Integral did not converge to the required accuracy!")
    return res, abserr


def _h_roots_newton(n, releps=3e-14, max_iter=10):
    # pim4=0.7511255444649425
    pim4 = np.pi ** (-1. / 4)

    # The roots are symmetric about the origin, so we have to
    # find only half of them.
    m = int(np.fix((n + 1) / 2))

    # Initial approximations to the roots go into z.
    anu = 2.0 * n + 1
    rhs = np.arange(3, 4 * m, 4) * np.pi / anu
    theta = _get_theta(rhs)
    z = sqrt(anu) * np.cos(theta)

    p = zeros((3, len(z)))
    k_0 = 0
    k_p1 = 1

    for _i in range(max_iter):
        # Newtons method carried out simultaneously on the roots.
        p[k_0, :] = 0
        p[k_p1, :] = pim4

        for j in range(1, n + 1):
            # Loop up the recurrence relation to get the Hermite
            # polynomials evaluated at z.
            k_m1 = k_0
            k_0 = k_p1
            k_p1 = np.mod(k_p1 + 1, 3)

            p[k_p1, :] = (z * sqrt(2 / j) * p[k_0, :] -
                          sqrt((j - 1) / j) * p[k_m1, :])

        # p now contains the desired Hermite polynomials.
        # We next compute p_deriv, the derivatives,
        # by the relation (4.5.21) using p2, the polynomials
        # of one lower order.

        p_deriv = sqrt(2 * n) * p[k_0, :]
        d_z = p[k_p1, :] / p_deriv

        z = z - d_z  # Newtons formula.

        converged = not np.any(abs(d_z) > releps)
        if converged:
            break

    _assert_warn(converged, 'Newton iteration did not converge!')
    weights = 2. / p_deriv ** 2
    return _expand_roots(z, weights, n, m)


def h_roots(n, method='newton'):
    """
    Returns the roots (x) of the nth order Hermite polynomial,
    H_n(x), and weights (w) to use in Gaussian Quadrature over
    [-inf,inf] with weighting function exp(-x**2).

    Parameters
    ----------
    n : integer
        number of roots
    method : 'newton' or 'eigenvalue'
        uses Newton Raphson to find zeros of the Hermite polynomial (Fast)
        or eigenvalue of the jacobi matrix (Slow) to obtain the nodes and
        weights, respectively.

    Returns
    -------
    x : ndarray
        roots
    w : ndarray
        weights

    Example
    -------
    >>> import numpy as np
    >>> x, w = h_roots(10)
    >>> np.allclose(np.sum(x*w), -5.2516042729766621e-19)
    True

    See also
    --------
    qrule, gaussq

    References
    ----------
    [1]  Golub, G. H. and Welsch, J. H. (1969)
    'Calculation of Gaussian Quadrature Rules'
    Mathematics of Computation, vol 23,page 221-230,

    [2]. Stroud and Secrest (1966), 'gaussian quadrature formulas',
      prentice-hall, Englewood cliffs, n.j.
    """

    if not method.startswith('n'):
        return ort.h_roots(n)
    return _h_roots_newton(n)


def _j_roots_newton(n, alpha, beta, releps=3e-14, max_iter=10):
    # Initial approximations to the roots go into z.
    alfbet = alpha + beta

    z = np.cos(np.pi * (np.arange(1, n + 1) - 0.25 + 0.5 * alpha) /
               (n + 0.5 * (alfbet + 1)))

    p = zeros((3, len(z)))
    k_0 = 0
    k_p1 = 1
    for _i in range(max_iter):
        # Newton's method carried out simultaneously on the roots.
        tmp = 2 + alfbet
        p[k_0, :] = 1
        p[k_p1, :] = (alpha - beta + tmp * z) / 2

        for j in range(2, n + 1):
            # Loop up the recurrence relation to get the Jacobi
            # polynomials evaluated at z.
            k_m1 = k_0
            k_0 = k_p1
            k_p1 = np.mod(k_p1 + 1, 3)

            a = 2. * j * (j + alfbet) * tmp
            tmp = tmp + 2
            c = 2 * (j - 1 + alpha) * (j - 1 + beta) * tmp
            b = (tmp - 1) * (alpha ** 2 - beta ** 2 + tmp * (tmp - 2) * z)

            p[k_p1, :] = (b * p[k_0, :] - c * p[k_m1, :]) / a

        # p now contains the desired Jacobi polynomials.
        # We next compute p_deriv, the derivatives with a standard
        # relation involving the polynomials of one lower order.

        p_deriv = ((n * (alpha - beta - tmp * z) * p[k_p1, :] +
                    2 * (n + alpha) * (n + beta) * p[k_0, :]) /
                   (tmp * (1 - z ** 2)))
        d_z = p[k_p1, :] / p_deriv
        z = z - d_z  # Newton's formula.

        converged = not any(abs(d_z) > releps * abs(z))
        if converged:
            break

    _assert_warn(converged, 'too many iterations in jrule')

    x = z  # Store the root and the weight.
    f = (sp.gammaln(alpha + n) + sp.gammaln(beta + n) -
         sp.gammaln(n + 1) - sp.gammaln(alpha + beta + n + 1))
    weights = (np.exp(f) * tmp * 2 ** alfbet / (p_deriv * p[k_0, :]))
    return x, weights


def j_roots(n, alpha, beta, method='newton'):
    """
    Returns the roots of the nth order Jacobi polynomial, P^(alpha,beta)_n(x)
    and weights (w) to use in Gaussian Quadrature over [-1,1] with weighting
    function (1-x)**alpha (1+x)**beta with alpha,beta > -1.

    Parameters
    ----------
    n : integer
        number of roots
    alpha,beta : scalars
        defining shape of Jacobi polynomial
    method : 'newton' or 'eigenvalue'
        uses Newton Raphson to find zeros of the Hermite polynomial (Fast)
        or eigenvalue of the jacobi matrix (Slow) to obtain the nodes and
        weights, respectively.

    Returns
    -------
    x : ndarray
        roots
    w : ndarray
        weights


    Example
    --------
    >>> [x,w]= j_roots(10,0,0)
    >>> sum(x*w)
    2.7755575615628914e-16

    See also
    --------
    qrule, gaussq


    Reference
    ---------
    [1]  Golub, G. H. and Welsch, J. H. (1969)
     'Calculation of Gaussian Quadrature Rules'
      Mathematics of Computation, vol 23,page 221-230,

    [2]. Stroud and Secrest (1966), 'gaussian quadrature formulas',
          prentice-hall, Englewood cliffs, n.j.
    """
    _assert((-1 < alpha) & (-1 < beta),
            'alpha and beta must be greater than -1')
    if not method.startswith('n'):
        return ort.j_roots(n, alpha, beta)
    return _j_roots_newton(n, alpha, beta)


def la_roots(n, alpha=0, method='newton'):
    """
    Returns the roots (x) of the nth order generalized (associated) Laguerre
    polynomial, L^(alpha)_n(x), and weights (w) to use in Gaussian quadrature
    over [0,inf] with weighting function exp(-x) x**alpha with alpha > -1.

    Parameters
    ----------
    n : integer
        number of roots
    method : 'newton' or 'eigenvalue'
        uses Newton Raphson to find zeros of the Laguerre polynomial (Fast)
        or eigenvalue of the jacobi matrix (Slow) to obtain the nodes and
        weights, respectively.

    Returns
    -------
    x : ndarray
        roots
    w : ndarray
        weights

    Example
    -------
    >>> import numpy as np
    >>> [x,w] = h_roots(10)
    >>> np.sum(x*w)
    1.3352627380516791e-17

    See also
    --------
    qrule, gaussq

    References
    ----------
    [1]  Golub, G. H. and Welsch, J. H. (1969)
    'Calculation of Gaussian Quadrature Rules'
    Mathematics of Computation, vol 23,page 221-230,

    [2]. Stroud and Secrest (1966), 'gaussian quadrature formulas',
      prentice-hall, Englewood cliffs, n.j.
    """
    _assert(-1 < alpha, 'alpha must be greater than -1')

    if not method.startswith('n'):
        return ort.la_roots(n, alpha)
    return _la_roots_newton(n, alpha)


def _get_theta(rhs):
    r_3 = rhs ** (1. / 3)
    r_2 = r_3 ** 2
    c = [9.084064e-01, 5.214976e-02, 2.579930e-03, 3.986126e-03]
    theta = r_3 * (c[0] + r_2 * (c[1] + r_2 * (c[2] + r_2 * c[3])))
    return theta


def _la_roots_newton(n, alpha, releps=3e-14, max_iter=10):

    # Initial approximations to the roots go into z.
    anu = 4.0 * n + 2.0 * alpha + 2.0
    rhs = np.arange(4 * n - 1, 2, -4) * np.pi / anu
    theta = _get_theta(rhs)
    z = anu * np.cos(theta) ** 2

    d_z = zeros(len(z))
    p = zeros((3, len(z)))
    p_previous = zeros((1, len(z)))
    p_deriv = zeros((1, len(z)))
    k_0 = 0
    k_p1 = 1
    k = slice(len(z))
    for _i in range(max_iter):
        # Newton's method carried out simultaneously on the roots.
        p[k_0, k] = 0.
        p[k_p1, k] = 1.

        for j in range(1, n + 1):
            # Loop up the recurrence relation to get the Laguerre
            # polynomials evaluated at z.
            km1 = k_0
            k_0 = k_p1
            k_p1 = np.mod(k_p1 + 1, 3)

            p[k_p1, k] = ((2 * j - 1 + alpha - z[k]) * p[k_0, k] -
                          (j - 1 + alpha) * p[km1, k]) / j
        # end
        # p now contains the desired Laguerre polynomials.
        # We next compute p_deriv, the derivatives with a standard
        # relation involving the polynomials of one lower order.

        p_previous[k] = p[k_0, k]
        p_deriv[k] = (n * p[k_p1, k] - (n + alpha) * p_previous[k]) / z[k]

        d_z[k] = p[k_p1, k] / p_deriv[k]
        z[k] = z[k] - d_z[k]  # Newton?s formula.
        # k = find((abs(d_z) > releps.*z))

        converged = not np.any(abs(d_z) > releps)
        if converged:
            break

    _assert_warn(converged, 'too many iterations!')

    nodes = z
    weights = -np.exp(sp.gammaln(alpha + n) -
                      sp.gammaln(n)) / (p_deriv * n * p_previous)
    return nodes, weights


def _p_roots_newton_start(n):
    m = int(np.fix((n + 1) / 2))
    t = (np.pi / (4 * n + 2)) * np.arange(3, 4 * m, 4)
    a = 1 - (1 - 1 / n) / (8 * n * n)
    x = a * np.cos(t)
    return m, x


def _p_roots_newton(n):
    """
    Algorithm given by Davis and Rabinowitz in 'Methods
    of Numerical Integration', page 365, Academic Press, 1975.
    """
    m, x = _p_roots_newton_start(n)

    e_1 = n * (n + 1)
    for _j in range(2):
        p_km1 = 1
        p_k = x
        for k in range(2, n + 1):
            t_1 = x * p_k
            p_kp1 = t_1 - p_km1 - (t_1 - p_km1) / k + t_1
            p_km1 = p_k
            p_k = p_kp1

        den = 1. - x * x
        d_1 = n * (p_km1 - x * p_k)
        d_pn = d_1 / den
        d_2pn = (2. * x * d_pn - e_1 * p_k) / den
        d_3pn = (4. * x * d_2pn + (2 - e_1) * d_pn) / den
        d_4pn = (6. * x * d_3pn + (6 - e_1) * d_2pn) / den
        u = p_k / d_pn
        v = d_2pn / d_pn
        h = -u * (1 + (.5 * u) * (v + u * (v * v - u * d_3pn / (3 * d_pn))))
        p = p_k + h * (d_pn + (.5 * h) * (d_2pn + (h / 3) *
                                          (d_3pn + .25 * h * d_4pn)))
        d_p = d_pn + h * (d_2pn + (.5 * h) * (d_3pn + h * d_4pn / 3))
        h = h - p / d_p
        x = x + h

    nodes = -x - h
    f_x = d_1 - h * e_1 * (p_k + (h / 2) * (d_pn + (h / 3) *
                                            (d_2pn + (h / 4) *
                                             (d_3pn + (.2 * h) * d_4pn))))
    weights = 2 * (1 - nodes ** 2) / (f_x ** 2)
    return _expand_roots(nodes, weights, n, m)


def _p_roots_newton1(n, releps=1e-15, max_iter=100):
    m, x = _p_roots_newton_start(n)
    # Compute the zeros of the N+1 Legendre Polynomial
    # using the recursion relation and the Newton-Raphson method

    # Legendre-Gauss Polynomials
    p = zeros((3, m))

    # Derivative of LGP
    p_deriv = zeros((m,))
    d_x = zeros((m,))

    # Compute the zeros of the N+1 Legendre Polynomial
    # using the recursion relation and the Newton-Raphson method

    # Iterate until new points are uniformly within epsilon of old
    # points
    k = slice(m)
    k_0 = 0
    k_p1 = 1
    for _ix in range(max_iter):
        p[k_0, k] = 1
        p[k_p1, k] = x[k]

        for j in range(2, n + 1):
            k_m1 = k_0
            k_0 = k_p1
            k_p1 = np.mod(k_0 + 1, 3)
            p[k_p1, k] = ((2 * j - 1) * x[k] * p[k_0, k] -
                          (j - 1) * p[k_m1, k]) / j

        p_deriv[k] = n * (p[k_0, k] - x[k] * p[k_p1, k]) / (1 - x[k] ** 2)

        d_x[k] = p[k_p1, k] / p_deriv[k]
        x[k] = x[k] - d_x[k]
        k, = np.nonzero((abs(d_x) > releps * np.abs(x)))
        converged = len(k) == 0
        if converged:
            break

    _assert(converged, 'Too many iterations!')

    nodes = -x
    weights = 2. / ((1 - nodes ** 2) * (p_deriv ** 2))
    return _expand_roots(nodes, weights, n, m)


def _expand_roots(x, w, n, m):
    if (m + m) > n:
        x[m - 1] = 0.0
    if not (m + m) == n:
        m = m - 1
    x = np.hstack((x, -x[m - 1::-1]))
    w = np.hstack((w, w[m - 1::-1]))
    return x, w


def p_roots(n, method='newton', a=-1, b=1):
    """
    Returns the roots (x) of the nth order Legendre polynomial, P_n(x),
    and weights to use in Gaussian Quadrature over [-1,1] with weighting
    function 1.

    Parameters
    ----------
    n : integer
        number of roots
    method : 'newton' or 'eigenvalue'
        uses Newton Raphson to find zeros of the Hermite polynomial (Fast)
        or eigenvalue of the jacobi matrix (Slow) to obtain the nodes and
        weights, respectively.

    Returns
    -------
    nodes : ndarray
        roots
    weights : ndarray
        weights


    Example
    -------
    Integral of exp(x) from a = 0 to b = 3 is: exp(3)-exp(0)=
    >>> import numpy as np
    >>> nodes, weights = p_roots(11, a=0, b=3)
    >>> np.allclose(np.sum(np.exp(nodes) * weights), 19.085536923187668)
    True
    >>> nodes, weights = p_roots(11, method='newton1', a=0, b=3)
    >>> np.allclose(np.sum(np.exp(nodes) * weights), 19.085536923187668)
    True
    >>> nodes, weights = p_roots(11, method='eigenvalue', a=0, b=3)
    >>> np.allclose(np.sum(np.exp(nodes) * weights), 19.085536923187668)
    True

    See also
    --------
    quadg.


    References
    ----------
    [1] Davis and Rabinowitz (1975) 'Methods of Numerical Integration',
        page 365, Academic Press.

    [2]  Golub, G. H. and Welsch, J. H. (1969)
        'Calculation of Gaussian Quadrature Rules'
        Mathematics of Computation, vol 23,page 221-230,

    [3] Stroud and Secrest (1966), 'gaussian quadrature formulas',
        prentice-hall, Englewood cliffs, n.j.
    """

    if not method.startswith('n'):
        nodes, weights = ort.p_roots(n)
    else:
        if method.endswith('1'):
            nodes, weights = _p_roots_newton1(n)
        else:
            nodes, weights = _p_roots_newton(n)

    if (a != -1) | (b != 1):
        # Linear map from[-1,1] to [a,b]
        d_h = (b - a) / 2
        nodes = d_h * (nodes + 1) + a
        weights = weights * d_h

    return nodes, weights


def q5_roots(n):
    """
    5      : p(x) = 1/sqrt((x-a)*(b-x)), a =-1,   b = 1 Chebyshev 1'st kind
    """
    j = np.arange(1, n + 1)
    weights = ones(n) * np.pi / n
    nodes = np.cos((2 * j - 1) * np.pi / (2 * n))
    return nodes, weights


def q6_roots(n):
    """
    6      : p(x) = sqrt((x-a)*(b-x)),   a =-1,   b = 1 Chebyshev 2'nd kind
    """
    j = np.arange(1, n + 1)
    x_j = j * np.pi / (n + 1)
    weights = np.pi / (n + 1) * np.sin(x_j) ** 2
    nodes = np.cos(x_j)
    return nodes, weights


def q7_roots(n):
    """
    7 : p(x) = sqrt((x-a)/(b-x)),   a = 0,   b = 1
    """
    j = np.arange(1, n + 1)
    x_j = (j - 0.5) * pi / (2 * n + 1)
    nodes = np.cos(x_j) ** 2
    weights = 2 * np.pi * nodes / (2 * n + 1)
    return nodes, weights


def q8_roots(n):
    """
    8 : p(x) = 1/sqrt(b-x),         a = 0,   b = 1
    """
    nodes_1, weights_1 = p_roots(2 * n)
    k, = np.where(0 <= nodes_1)
    weights = 2 * weights_1[k]
    nodes = 1 - nodes_1[k] ** 2
    return nodes, weights


def q9_roots(n):
    """
     9 : p(x) = sqrt(b-x),           a = 0,   b = 1
    """
    nodes_1, weights_1 = p_roots(2 * n + 1)
    k, = np.where(0 < nodes_1)
    weights = 2 * nodes_1[k] ** 2 * weights_1[k]
    nodes = 1 - nodes_1[k] ** 2
    return nodes, weights


def qrule(n, wfun=1, alpha=0, beta=0):
    """
    Return nodes and weights for Gaussian quadratures.

    Parameters
    ----------
    n : integer
        number of base points
    wfun : integer
        defining the weight function, p(x). (default wfun = 1)
        1 : p(x) = 1                       a =-1,   b = 1  Gauss-Legendre
        2 : p(x) = exp(-x^2)               a =-inf, b = inf Hermite
        3 : p(x) = x^alpha*exp(-x)         a = 0,   b = inf Laguerre
        4 : p(x) = (x-a)^alpha*(b-x)^beta  a =-1,   b = 1 Jacobi
        5 : p(x) = 1/sqrt((x-a)*(b-x)), a =-1,   b = 1 Chebyshev 1'st kind
        6 : p(x) = sqrt((x-a)*(b-x)),   a =-1,   b = 1 Chebyshev 2'nd kind
        7 : p(x) = sqrt((x-a)/(b-x)),   a = 0,   b = 1
        8 : p(x) = 1/sqrt(b-x),         a = 0,   b = 1
        9 : p(x) = sqrt(b-x),           a = 0,   b = 1

    Returns
    -------
    bp = base points (abscissas)
    wf = weight factors

    The Gaussian Quadrature integrates a (2n-1)th order
    polynomial exactly and the integral is of the form
               b                         n
              Int ( p(x)* F(x) ) dx  =  Sum ( wf_j* F( bp_j ) )
               a                        j=1
    where p(x) is the weight function.
    For Jacobi and Laguerre: alpha, beta >-1 (default alpha=beta=0)

    Examples:
    ---------
    >>> import numpy as np

    # integral of x^2 from a = -1 to b = 1
    >>> bp, wf = qrule(10)
    >>> np.allclose(sum(bp**2*wf), 0.66666666666666641)
    True

    # integral of exp(-x**2)*x**2 from a = -inf to b = inf
    >>> bp, wf = qrule(10,2)
    >>> np.allclose(sum(bp ** 2 * wf), 0.88622692545275772)
    True

    # integral of (x+1)*(1-x)**2 from  a = -1 to b = 1
    >>> bp, wf = qrule(10,4,1,2)
    >>> np.allclose((bp * wf).sum(), 0.26666666666666755)
    True

    See also
    --------
    gaussq

    Reference
    ---------
    Abromowitz and Stegun (1954)
    (for method 5 to 9)
    """

    if wfun == 3:  # Generalized Laguerre
        return la_roots(n, alpha)
    if wfun == 4:  # Gauss-Jacobi
        return j_roots(n, alpha, beta)

    _assert(0 < wfun < 10, 'unknown weight function')

    root_fun = [None, p_roots, h_roots, la_roots, j_roots, q5_roots, q6_roots,
                q7_roots, q8_roots, q9_roots][wfun]
    return root_fun(n)


class _Gaussq(object):
    """
    Numerically evaluate integral, Gauss quadrature.

    Parameters
    ----------
    fun : callable
    a,b : array-like
        lower and upper integration limits, respectively.
    releps, abseps : real scalars, optional
        relative and absolute tolerance, respectively.
        (default releps=abseps=1e-3).
    wfun : scalar integer, optional
        defining the weight function, p(x). (default wfun = 1)
        1 : p(x) = 1                       a =-1,   b = 1   Gauss-Legendre
        2 : p(x) = exp(-x^2)               a =-inf, b = inf Hermite
        3 : p(x) = x^alpha*exp(-x)         a = 0,   b = inf Laguerre
        4 : p(x) = (x-a)^alpha*(b-x)^beta  a =-1,   b = 1 Jacobi
        5 : p(x) = 1/sqrt((x-a)*(b-x)),    a =-1,   b = 1 Chebyshev 1'st kind
        6 : p(x) = sqrt((x-a)*(b-x)),      a =-1,   b = 1 Chebyshev 2'nd kind
        7 : p(x) = sqrt((x-a)/(b-x)),      a = 0,   b = 1
        8 : p(x) = 1/sqrt(b-x),            a = 0,   b = 1
        9 : p(x) = sqrt(b-x),              a = 0,   b = 1
    trace : bool, optional
        If non-zero a point plot of the integrand (default False).
    gn : scalar integer
        number of base points to start the integration with (default 2).
    alpha, beta : real scalars, optional
        Shape parameters of Laguerre or Jacobi weight function
        (alpha,beta>-1) (default alpha=beta=0)

    Returns
    -------
    val : ndarray
        evaluated integral
    err : ndarray
        error estimate, absolute tolerance abs(int-intold)

    Notes
    -----
    GAUSSQ numerically evaluate integral using a Gauss quadrature.
    The Quadrature integrates a (2m-1)th order polynomial exactly and the
    integral is of the form
             b
             Int (p(x)* Fun(x)) dx
              a
    GAUSSQ is vectorized to accept integration limits A, B and
    coefficients P1,P2,...Pn, as matrices or scalars and the
    result is the common size of A, B and P1,P2,...,Pn.

    Examples
    ---------
    integration of x**2        from 0 to 2 and from 1 to 4

    >>> import numpy as np
    >>> A = [0, 1]
    >>> B = [2, 4]
    >>> fun = lambda x: x**2
    >>> val1, err1 = gaussq(fun,A,B)
    >>> np.allclose(val1, [  2.6666667,  21.       ])
    True
    >>> np.allclose(err1, [  1.7763568e-15,   1.0658141e-14])
    True

    Integration of x^2*exp(-x) from zero to infinity:
    >>> fun2 = lambda x : np.ones(np.shape(x))
    >>> val2, err2 = gaussq(fun2, 0, np.inf, wfun=3, alpha=2)
    >>> val3, err3 = gaussq(lambda x: x**2,0, np.inf, wfun=3, alpha=0)
    >>> np.allclose(val2, 2),  err2[0] < 1e-14
    (True, True)
    >>> np.allclose(val3, 2), err3[0] < 1e-14
    (True, True)

    Integrate humps from 0 to 2 and from 1 to 4
    >>> val4, err4 = gaussq(humps, A, B, trace=True)

    See also
    --------
    qrule
    gaussq2d
    """

    @staticmethod
    def _get_dx(wfun, jacob, alpha, beta):
        def fun1(x):
            return x
        if wfun == 4:
            d_x = jacob ** (alpha + beta + 1)
        else:
            d_x = [None, fun1, fun1, fun1, None, lambda x: ones(np.shape(x)),
                   lambda x: x ** 2, fun1, sqrt,
                   lambda x: sqrt(x) ** 3][wfun](jacob)
        return d_x.ravel()

    @staticmethod
    def _nodes_and_weights(num_nodes, wfun, alpha, beta):
        global _NODES_AND_WEIGHTS
        name = 'wfun{:d}_{:d}_{:g}_{:g}'.format(wfun, num_nodes, alpha, beta)
        nodes_and_weights = _NODES_AND_WEIGHTS[name]
        if len(nodes_and_weights) == 0:
            nodes_and_weights.extend(qrule(num_nodes, wfun, alpha, beta))
        nodes, weights = nodes_and_weights
        return nodes, weights

    def _initialize_trace(self, max_iter):
        if self.trace:
            self.x_trace = [0] * max_iter
            self.y_trace = [0] * max_iter

    def _plot_trace(self, x, y):
        if self.trace:
            self.x_trace.append(x.ravel())
            self.y_trace.append(y.ravel())
            hfig = plt.plot(x, y, 'r.')
            plt.setp(hfig, 'color', 'b')

    def _plot_final_trace(self):
        if self.trace > 0:
            plt.clf()
            plt.plot(np.hstack(self.x_trace), np.hstack(self.y_trace), '+')

    @staticmethod
    def _get_jacob(wfun, a, b):
        if wfun in [2, 3]:
            jacob = ones((np.size(a), 1))
        else:
            jacob = (b - a) * 0.5
            if wfun in [7, 8, 9]:
                jacob *= 2
        return jacob

    @staticmethod
    def _warn_msg(k, a_shape):
        n = len(k)
        if n > 1:
            if n == np.prod(a_shape):
                msg = 'All integrals did not converge'
            else:
                msg = '%d integrals did not converge' % (n, )
            return msg + '--singularities likely!'
        return 'Integral did not converge--singularity likely!'

    @staticmethod
    def _initialize(wfun, a, b, args):
        args = np.broadcast_arrays(*np.atleast_1d(a, b, *args))
        a_shape = args[0].shape
        args = [np.reshape(x, (-1, 1)) for x in args]
        a_out, b_out = args[:2]
        args = args[2:]
        if wfun in [2, 3]:
            a_out = zeros((a_out.size, 1))
        return a_out, b_out, args, a_shape

    @staticmethod
    def _revert_nans_with_old(val, val_old):
        if any(np.isnan(val)):
            val[np.isnan(val)] = val_old[np.isnan(val)]

    @staticmethod
    def _update_error(i, abserr, val, val_old, k):
        if i > 1:
            abserr[k] = abs(val_old[k] - val[k])  # absolute tolerance

    def __call__(self, fun, a, b, releps=1e-3, abseps=1e-3, alpha=0, beta=0,
                 wfun=1, trace=False, args=(), max_iter=11):
        self.trace = trace
        num_nodes = 2

        a_0, b_0, args, a_shape = self._initialize(wfun, a, b, args)

        jacob = self._get_jacob(wfun, a_0, b_0)
        shift = int(wfun in [1, 4, 5, 6])
        d_x = self._get_dx(wfun, jacob, alpha, beta)

        self._initialize_trace(max_iter)

        # Break out of the iteration loop for three reasons:
        #  1) the last update is very small (compared to int and to releps)
        #  2) There are more than 11 iterations. This should NEVER happen.
        dtype = np.result_type(fun((a_0+b_0)*0.5, *args))
        n_k = np.prod(a_shape)  # # of integrals we have to compute
        k = np.arange(n_k)
        opt = (n_k, dtype)
        val, val_old, abserr = zeros(*opt), np.nan*ones(*opt), 1e100*ones(*opt)
        nodes_and_weights = self._nodes_and_weights
        for i in range(max_iter):
            x_n, weights = nodes_and_weights(num_nodes, wfun, alpha, beta)
            x = (x_n + shift) * jacob[k, :] + a_0[k, :]

            params = [xi[k, :] for xi in args]
            y = fun(x, *params)
            self._plot_trace(x, y)
            val[k] = np.sum(weights * y, axis=1) * d_x[k]  # do the integration
            self._revert_nans_with_old(val, val_old)
            self._update_error(i, abserr, val, val_old, k)

            k, = np.where(abserr > np.maximum(abs(releps * val), abseps))
            converged = len(k) == 0
            if converged:
                break
            val_old[k] = val[k]
            num_nodes *= 2  # double the # of basepoints and weights

        _assert_warn(converged, self._warn_msg(k, a_shape))

        # make sure int is the same size as the integration  limits
        val.shape = a_shape
        abserr.shape = a_shape

        self._plot_final_trace()
        return val, abserr
gaussq = _Gaussq()


def richardson(q_val, k):
    # license BSD
    # Richardson extrapolation with parameter estimation
    c = np.real((q_val[k - 1] - q_val[k - 2]) / (q_val[k] - q_val[k - 1])) - 1.
    # The lower bound 0.07 admits the singularity x.^-0.9
    c = max(c, 0.07)
    return q_val[k] + (q_val[k] - q_val[k - 1]) / c


class _Quadgr(object):
    """
    Gauss-Legendre quadrature with Richardson extrapolation.

    [q_val,ERR] = QUADGR(FUN,A,B,TOL) approximates the integral of a function
    FUN from A to B with an absolute error tolerance TOL. FUN is a function
    handle and must accept vector arguments. TOL is 1e-6 by default. q_val is
    the integral approximation and ERR is an estimate of the absolute
    error.

    QUADGR uses a 12-point Gauss-Legendre quadrature. The error estimate is
    based on successive interval bisection. Richardson extrapolation
    accelerates the convergence for some integrals, especially integrals
    with endpoint singularities.

    Examples
    --------
    >>> import numpy as np
    >>> q_val, err = quadgr(np.log,0,1)
    >>> q, err = quadgr(np.exp,0,9999*1j*np.pi)
    >>> np.allclose(q, -2.0000000000122662), err < 1.0e-08
    (True, True)

    >>> q, err = quadgr(lambda x: np.sqrt(4-x**2), 0, 2, abseps=1e-12)
    >>> np.allclose(q, 3.1415926535897811), err < 1.0e-12
    (True, True)

    >>> q, err = quadgr(lambda x: np.sqrt(4-x**2), 0, 0, abseps=1e-12)
    >>> np.allclose(q, 0), err < 1.0e-12
    (True, True)

    >>> q, err = quadgr(lambda x: x**-0.75, 0, 1)
    >>> np.allclose(q, 4), err < 1.e-13
    (True, True)

    >>> q, err = quadgr(lambda x: 1./np.sqrt(1-x**2), -1, 1)
    >>> np.allclose(q, 3.141596056985029), err < 1.0e-05
    (True, True)

    >>> q, err = quadgr(lambda x: np.exp(-x**2), -np.inf, np.inf, 1e-9)
    >>> np.allclose(q, np.sqrt(np.pi)), err < 1e-9
    (True, True)

    >>> q, err = quadgr(lambda x: np.cos(x)*np.exp(-x), 0, np.inf, 1e-9)
    >>> np.allclose(q, 0.5), err < 1e-9
    (True, True)
    >>> q, err = quadgr(lambda x: np.cos(x)*np.exp(-x), np.inf, 0, 1e-9)
    >>> np.allclose(q, -0.5), err < 1e-9
    (True, True)
    >>> q, err = quadgr(lambda x: np.cos(x)*np.exp(x), -np.inf, 0, 1e-9)
    >>> np.allclose(q, 0.5), err < 1e-9
    (True, True)

    See also
    --------
    QUAD,
    QUADGK
    """
    # Author: jonas.lundgren@saabgroup.com, 2009. license BSD
    # Order limits (required if infinite limits)

    def _change_variable_and_integrate(self, fun, a, b, abseps, max_iter):
        isreal = np.isreal(a) & np.isreal(b) & ~np.isnan(a) & ~np.isnan(b)
        _assert(isreal, 'Infinite intervals must be real.')
        integrate = self._integrate
        # Change of variable
        if np.isfinite(a) & np.isinf(b):  # a to inf
            val, err = integrate(lambda t: fun(a + t / (1 - t)) / (1 - t) ** 2,
                                 0, 1, abseps, max_iter)
        elif np.isinf(a) & np.isfinite(b):  # -inf to b
            val, err = integrate(lambda t: fun(b + t / (1 + t)) / (1 + t) ** 2,
                                 -1, 0, abseps, max_iter)
        else:  # -inf to inf
            val1, err1 = integrate(lambda t: fun(t / (1 - t)) / (1 - t) ** 2,
                                   0, 1, abseps / 2, max_iter)
            val2, err2 = integrate(lambda t: fun(t / (1 + t)) / (1 + t) ** 2,
                                   -1, 0, abseps / 2, max_iter)
            val = val1 + val2
            err = err1 + err2
        return val, err

    @staticmethod
    def _nodes_and_weights():
        # Gauss-Legendre quadrature (12-point)
        x = np.asarray(
            [0.12523340851146894, 0.36783149899818018, 0.58731795428661748,
             0.76990267419430469, 0.9041172563704748, 0.98156063424671924])
        w = np.asarray(
            [0.24914704581340288, 0.23349253653835478, 0.20316742672306584,
             0.16007832854334636, 0.10693932599531818, 0.047175336386511842])
        nodes = np.hstack((x, -x))
        weights = np.hstack((w, w))
        return nodes, weights

    @staticmethod
    def _get_best_estimate(k, vals0, vals1, vals2):
        if k >= 6:
            q_v = np.hstack((vals0[k], vals1[k], vals2[k]))
            q_w = np.hstack((vals0[k - 1], vals1[k - 1], vals2[k - 1]))
        elif k >= 4:
            q_v = np.hstack((vals0[k], vals1[k]))
            q_w = np.hstack((vals0[k - 1], vals1[k - 1]))
        else:
            q_v = np.atleast_1d(vals0[k])
            q_w = vals0[k - 1]
        # Estimate absolute error
        errors = np.atleast_1d(abs(q_v - q_w))
        j = errors.argmin()
        err = errors[j]
        q_val = q_v[j]
#         if k >= 2: # and not iscomplex:
#             _val, err1 = dea3(vals0[k - 2], vals0[k - 1], vals0[k])
        return q_val, err

    def _extrapolate(self, k, val0, val1, val2):
        # Richardson extrapolation
        if k >= 5:
            val1[k] = richardson(val0, k)
            val2[k] = richardson(val1, k)
        elif k >= 3:
            val1[k] = richardson(val0, k)
        q_val, err = self._get_best_estimate(k, val0, val1, val2)
        return q_val, err

    def _integrate(self, fun, a, b, abseps, max_iter):
        dtype = np.result_type(fun((a+b)/2), fun((a+b)/4))

        # Initiate vectors
        val0 = zeros(max_iter, dtype=dtype)  # Quadrature
        val1 = zeros(max_iter, dtype=dtype)  # First Richardson extrapolation
        val2 = zeros(max_iter, dtype=dtype)  # Second Richardson extrapolation

        x_n, weights = self._nodes_and_weights()
        n = len(x_n)
        # One interval
        d_x = (b - a) / 2                # Half interval length
        x = (a + b) / 2 + d_x * x_n      # Nodes
        # Quadrature
        val0[0] = d_x * np.sum(weights * fun(x), axis=0)

        # Successive bisection of intervals
        for k in range(1, max_iter):

            # Interval bisection
            d_x = d_x / 2
            x = np.hstack([x + a, x + b]) / 2
            # Quadrature
            val0[k] = np.sum(np.sum(np.reshape(fun(x), (-1, n)), axis=0) *
                             weights, axis=0) * d_x

            q_val, err = self._extrapolate(k, val0, val1, val2)

            converged = (err < abseps) | ~np.isfinite(q_val)
            if converged:
                break

        _assert_warn(converged, 'Max number of iterations reached without '
                     'convergence.')
        _assert_warn(np.isfinite(q_val),
                     'Integral approximation is Infinite or NaN.')

        # The error estimate should not be zero
        err = err + 2 * np.finfo(q_val).eps
        return q_val, err

    @staticmethod
    def _order_limits(a, b):
        if np.real(a) > np.real(b):
            return b, a, True
        return a, b, False

    def __call__(self, fun, a, b, abseps=1e-5, max_iter=17):
        a = np.asarray(a)
        b = np.asarray(b)
        if a == b:
            q_val = b - a
            err = np.abs(b - a)
            return q_val, err

        a, b, reverse = self._order_limits(a, b)

        improper_integral = np.isinf(a) | np.isinf(b)
        if improper_integral:  # Infinite limits
            q_val, err = self._change_variable_and_integrate(fun, a, b, abseps,
                                                             max_iter)
        else:
            q_val, err = self._integrate(fun, a, b, abseps, max_iter)

        # Reverse direction
        if reverse:
            q_val = -q_val

        return q_val, err

quadgr = _Quadgr()


def boole(y, x):
    a, b = x[0], x[-1]
    n = len(x)
    h = (b - a) / (n - 1)
    return (2 * h / 45) * (7 * (y[0] + y[-1]) + 12 * np.sum(y[2:n - 1:4]) +
                           32 * np.sum(y[1:n - 1:2]) +
                           14 * np.sum(y[4:n - 3:4]))


def _plot_error(neval, err_dic, plot_error):
    if plot_error:
        plt.figure(0)
        for name in err_dic:
            plt.loglog(neval, err_dic[name], label=name)

        plt.xlabel('number of function evaluations')
        plt.ylabel('error')
        plt.legend()


def _print_headers(formats_h, headers, names):
    print(''.join(fi % t for (fi, t) in zip(formats_h,
                                            ['ftn'] + names)))
    print(' '.join(headers))


def _stack_values_and_errors(neval, vals_dic, err_dic, names):
    data = [neval]
    for name in names:
        data.append(vals_dic[name])
        data.append(err_dic[name])

    data = np.vstack(tuple(data)).T
    return data


def _print_data(formats, data):
    for row in data:
        print(''.join(fi % t for (fi, t) in zip(formats, row.tolist())))


def _print_values_and_errors(neval, vals_dic, err_dic):
    names = sorted(vals_dic.keys())
    num_cols = 2
    formats = ['%4.0f, '] + ['%10.10f, '] * num_cols * 2
    formats[-1] = formats[-1].split(',')[0]
    formats_h = ['%4s, '] + ['%20s, '] * num_cols
    formats_h[-1] = formats_h[-1].split(',')[0]
    headers = ['evals'] + ['%12s %12s' % ('approx', 'error')] * num_cols
    while len(names) > 0:
        names_c = names[:num_cols]
        _print_headers(formats_h, headers, names_c)
        data = _stack_values_and_errors(neval, vals_dic, err_dic, names_c)
        _print_data(formats, data)
        names = names[num_cols:]


def _display(neval, vals_dic, err_dic, plot_error):
    # display results
    _print_values_and_errors(neval, vals_dic, err_dic)
    _plot_error(neval, err_dic, plot_error)


def chebychev(y, x, n=None):
    if n is None:
        n = len(y)
    c_k = np.polynomial.chebyshev.chebfit(x, y, deg=min(n - 1, 36))
    c_ki = np.polynomial.chebyshev.chebint(c_k)
    q = np.polynomial.chebyshev.chebval(x[-1], c_ki)
    return q


def qdemo(f, a, b, kmax=9, plot_error=False):
    """
    Compares different quadrature rules.

    Parameters
    ----------
    f : callable
        function
    a,b : scalars
        lower and upper integration limits

    Details
    -------
    qdemo(f,a,b) computes and compares various approximations to
    the integral of f from a to b.  Three approximations are used,
    the composite trapezoid, Simpson's, and Boole's rules, all with
    equal length subintervals.
    In a case like qdemo(exp,0,3) one can see the expected
    convergence rates for each of the three methods.
    In a case like qdemo(sqrt,0,3), the convergence rate is limited
    not by the method, but by the singularity of the integrand.

    Example
    -------
    >>> import numpy as np
    >>> qdemo(np.exp,0,3, plot_error=True)
    true value =  19.08553692
     ftn,                Boole,            Chebychev
    evals       approx        error       approx        error
       3, 19.4008539142, 0.3153169910, 19.5061466023, 0.4206096791
       5, 19.0910191534, 0.0054822302, 19.0910191534, 0.0054822302
       9, 19.0856414320, 0.0001045088, 19.0855374134, 0.0000004902
      17, 19.0855386464, 0.0000017232, 19.0855369232, 0.0000000000
      33, 19.0855369505, 0.0000000273, 19.0855369232, 0.0000000000
      65, 19.0855369236, 0.0000000004, 19.0855369232, 0.0000000000
     129, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
     257, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
     513, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
     ftn,      Clenshaw-Curtis,       Gauss-Legendre
    evals       approx        error       approx        error
       3, 19.5061466023, 0.4206096791, 19.0803304585, 0.0052064647
       5, 19.0834145766, 0.0021223465, 19.0855365951, 0.0000003281
       9, 19.0855369150, 0.0000000082, 19.0855369232, 0.0000000000
      17, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
      33, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
      65, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
     129, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
     257, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
     513, 19.0855369232, 0.0000000000, 19.0855369232, 0.0000000000
     ftn,                Simps,                Trapz
    evals       approx        error       approx        error
       3, 19.5061466023, 0.4206096791, 22.5366862979, 3.4511493747
       5, 19.1169646189, 0.0314276957, 19.9718950387, 0.8863581155
       9, 19.0875991312, 0.0020622080, 19.3086731081, 0.2231361849
      17, 19.0856674267, 0.0001305035, 19.1414188470, 0.0558819239
      33, 19.0855451052, 0.0000081821, 19.0995135407, 0.0139766175
      65, 19.0855374350, 0.0000005118, 19.0890314614, 0.0034945382
     129, 19.0855369552, 0.0000000320, 19.0864105817, 0.0008736585
     257, 19.0855369252, 0.0000000020, 19.0857553393, 0.0002184161
     513, 19.0855369233, 0.0000000001, 19.0855915273, 0.0000546041
    """
    true_val, _tol = intg.quad(f, a, b)
    print('true value = %12.8f' % (true_val,))
    neval = zeros(kmax, dtype=int)
    vals_dic = {}
    err_dic = {}

    # try various approximations
    methods = [trapz, simps, boole, chebychev]

    for k in range(kmax):
        n = 2 ** (k + 1) + 1
        neval[k] = n
        x = np.linspace(a, b, n)
        y = f(x)
        for method in methods:
            name = method.__name__.title()
            q = method(y, x)
            vals_dic.setdefault(name, []).append(q)
            err_dic.setdefault(name, []).append(abs(q - true_val))

        name = 'Clenshaw-Curtis'
        q = clencurt(f, a, b, (n - 1) // 2)[0]
        vals_dic.setdefault(name, []).append(q[0])
        err_dic.setdefault(name, []).append(abs(q[0] - true_val))

        name = 'Gauss-Legendre'  # quadrature
        q = intg.fixed_quad(f, a, b, n=n)[0]
        vals_dic.setdefault(name, []).append(q)
        err_dic.setdefault(name, []).append(abs(q - true_val))

    _display(neval, vals_dic, err_dic, plot_error)


def main():
    #    val, err = clencurt(np.exp, 0, 2)
    #    valt = np.exp(2) - np.exp(0)
    #    [Q, err] = quadgr(lambda x: x ** 2, 1, 4, 1e-9)
    #    [Q, err] = quadgr(humps, 1, 4, 1e-9)
    #
    #    [x, w] = h_roots(11, 'newton')
    #    sum(w)
    #    [x2, w2] = la_roots(11, 1, 't')
    #
    # from scitools import numpyutils as npu #@UnresolvedImport
    #    fun = npu.wrap2callable('x**2')
    #    p0 = fun(0)
    #    A = [0, 1, 1]; B = [2, 4, 3]
    #    area, err = gaussq(fun, A, B)
    #
    #    fun = npu.wrap2callable('x**2')
    #    [val1, err1] = gaussq(fun, A, B)
    #
    #
    # Integration of x^2*exp(-x) from zero to infinity:
    #    fun2 = npu.wrap2callable('1')
    #    [val2, err2] = gaussq(fun2, 0, np.inf, wfun=3, alpha=2)
    #    [val2, err2] = gaussq(lambda x: x ** 2, 0, np.inf, wfun=3, alpha=0)
    #
    # Integrate humps from 0 to 2 and from 1 to 4
    #    [val3, err3] = gaussq(humps, A, B)
    #
    #    [x, w] = p_roots(11, 'newton', 1, 3)
    #    y = np.sum(x ** 2 * w)

    x = np.linspace(0, np.pi / 2)
    _q0 = np.trapz(humps(x), x)
    [q, err] = romberg(humps, 0, np.pi / 2, 1e-4)
    print(q, err)


if __name__ == '__main__':
    from wafo.testing import test_docstrings
    test_docstrings(__file__)
    # qdemo(np.exp, 0, 3, plot_error=True)
    # plt.show('hold')
    # main()
