.. _gpc: Generalized Polynomial Chaos ============================ .. module:: best.gpc :synopsis: Classes and methods for the construction of Generalized Polynomial Chaos (gPC) with arbitrary probability distributions. The module :mod:`best.gpc` implements functionality related to the construction and manipulation of orthogonal polynomials with respect to arbitrary probability distributions. Mathematically, let :math:`f(\cdot)` be a probability distribution. Then it allows the construction of polynomials :math:`\phi_n(\cdot)` such that: .. math:: \int \phi_n(\mathbf{x})\phi_m(\mathbf{x})f(\mathbf{x})d\mathbf{x} = \delta_{nm}. It is largly based on the legacy Fortran code ORTHPOL. 1D Orthogonal Polynomials ------------------------- We start with the construction of polynomials in one dimension. This can be achieved via the class :class:`best.gpc.OrthogonalPolynomial` which is described below. .. class:: best.gpc.OrthogonalPolynomial A class representing a 1D orthogonal polynomial with respect to a \ particular probability density. The polynomials are described internally via the recursive relations: .. math:: \phi_n(x) = \left((x - \alpha_{n-1})\phi_{n-1}(x) - \beta_{n-1}\phi_{n-2}(x)\right) / \gamma_{n}, with .. math:: \phi_0(x) = 1 / \gamma_0 and .. math:: \phi_1(x) = (x - \alpha_0)\phi_0(x) / \gamma_1. They are always properly normalized. Keep in mind that it inherits from :class:`best.maps.Function`, so it is a multi-output function. The number of outputs is essentially equal to the number of polynomials represented by the class. .. method:: __init__(degree[, rv=None[, left=-1[, right=1[, wf=lambda(x): 1.[, \ ncap=50[, quad=QuadratureRule[, \ name='Orthogonal Polynomial']]]]]]]) Initialize the object. The polynomial is constructed on an interval :math:`[a, b]`, where :math:`-\infty \le a < b \le +\infty`. The construction of the polynomial uses the Lanczos procedure (see :func:`best.gpc.lancz()`) which relies on a quadrature rule. The default quadrature rule is the n-point Fejer rule (see :func:`best.gpc.fejer()`). These default can, of course, be bypassed by overloading this class. :param degree: The degree of the polynomial. :type degree: int :param rv: A 1D random variable. It can be anything that inherits \ from ``scipy.stats.rv_continuous``. If this is \ specified, the rest of the keyword arguments are \ ignored. :type rv: ``scipy.stats.rv_continuous`` :param left: The left side of the interval over which the \ polynomial is defined. :math:`-\infty` may be specified by ``-float('inf')``. :type left: float :param right: The right side of the interval over which the \ polynomial is defined. :math:`\infty` may be specified by ``float('inf')``. :type right: float :param f: The probability density serving as weight. :type wf: A real function of one variable. :param ncap: The number of quadrature points to be used. :type ncap: int :param quad: A quadrature rule. See the description below. :type quad: :class:`best.gpc.QuadratureRule` :param name: A name for the polynomials. :type name: str .. attribute:: degree The maximum degree of the polynomials. The number of polynomials is ``degree + 1``. .. attribute:: alpha The :math:`\alpha_n` coefficients of the recursive relation. .. attribute:: beta The :math:`\beta_n` coefficients of the recursive relation. .. attribute:: gamma The :math:`\gamma_n` coefficients of the recursive relation. .. is_normalized() Return ``True`` if the polynomials have unit norm and ``False`` \ otherwise. :Note: If you use the default constructor the polynomials are \ automatically normalized. .. normalize() Normalize the polynomials so that they have unit norm. .. method:: _eval(x): Evaluate the function at ``x`` assuming that ``x`` has the right dimensions. .. note:: Overloaded version of :func:`best.maps.Function._eval()` :param x: The evaluation point. :type x: 1D numpy array of the right dimensions :returns: The result. :rtype: 1D numpy array of the right dimensions or just a float .. method:: _d_eval(x): Evaluate the Jacobian of the function at ``x``. The dimensions of the Jacobian are ``num_output x num_input``. .. note:: Overloaded version of :func:`best.maps.Function._d_eval()` :param x: The evaluation point. :type x: 1D numpy array of the right dimensions :returns: The Jacobian at x. :rtype: 2D numpy array of the right dimensions Product Basis ------------- 1D polynomials can be combined to create multi-input polynomials. The class :class:`best.gpc.ProductBasis` constructs multi-input orthogonal polynomials up to a given degree by combining 1D orthogonal polynomials. Here is its definition: .. class:: best.gpc.ProductBasis A multi-input orthogonal polynomial basis. It inherits from :class:`best.maps.Function`. .. method:: __init__([rv=None[, degree=1[, polynomials=None[, \ ncap=50[, quad=None[, name='Product Basis']]]]]]) Initialize the object. :param rv: A random vector of independent random variables. If \ not ``None``, then the keyword argument ``polynomials`` is ignored. :type rv: :class:`best.random.RandomVectorIndependent` :param degree: The total degree of the basis. Each one of the \ 1D polynomials will have this degree. It is \ ignored if ``rv`` is ``None``. :type degree: int :param polynomials: We only look at this if ``rv`` is ``None``. \ It is a collection of 1D orthogonal polynomials. :type polynomials: tuple or list of :class:`best.gpc.OrthogonalPolynomial` :param ncap: The number of quadrature points for each dimension. :type ncap: int :param quad: The quadrature rule you might want to use. :param name: A name for the basis. :type name: str .. attribute:: degree The total order of the basis. .. attribute:: polynomials The container of 1D polynomials. .. attribute:: terms An array representing the basis terms. .. attribute:: num_terms The number of of terms up to each order .. method:: _eval(x): Evaluate the function at ``x`` assuming that ``x`` has the right dimensions. .. note:: Overloaded version of :func:`best.maps.Function._eval()` :param x: The evaluation point. :type x: 1D numpy array of the right dimensions :returns: The result. :rtype: 1D numpy array of the right dimensions or just a float Constructing Polynomials ------------------------ We now show how to construct several of the standar orthogonal polynomials used in the literature. For convenience, assume that in all examples we have imported the ``matplotlib.pyplot`` of the `matplotlib library `_ by:: import matplotlib.pyplot as plt Hermite polynomials +++++++++++++++++++ The Hermite polynomials are defined on :math:`(-\infty, \infty)` are orthogonal with respect to the probability density: .. math:: f(x) = \frac{1}{\sqrt{2\pi}}e^{-x^2/2}. Here is how you can construct them up to degree 10:: from best.gpc import OrthogonalPolynomial infty = float('inf') # A number representing infinty. degree = 10 # The degree of the polynomials wf = lambda(x): 1. / math.sqrt(2. * math.pi) * np.exp(-x ** 2 / 2.) p = OrthogonalPolynomial(degree, left=-infty, right=infty, wf=wf) Notice the definition of the probability density function. This could be a regular function or a :class:`best.maps.Function`. Here we have opted for the much quicker lambda structure. You may look at the coefficients of the recursive formula by:: print str(p) which should produce the following text:: Orthogonal Polynomial:R^1 --> R^11 alpha: [ 8.24721933e-17 3.00634774e-16 -1.87973171e-16 3.50005961e-16 -5.28859926e-16 4.05750024e-16 -6.69888614e-16 -8.13357045e-16 8.06209016e-16 -2.21298838e-15 7.45252594e-16] beta: [ 1.00000012 1.00000151 1.41419988 1.73184818 2.0000218 2.24118187 2.45731915 2.59905334 2.71187277 3.16796779 3.68960306] gamma: [ 1.00000012 1.00000151 1.41419988 1.73184818 2.0000218 2.24118187 2.45731915 2.59905334 2.71187277 3.16796779 3.68960306] normalized: True You can see the polynomials by:: x = np.linspace(-2., 2., 100) plt.plot(x, p(x)) plt.show() which should produce the following figure: .. figure:: images/hermite.png :align: center The Hermite polynomials up to degree 10. Similarly you may visualize their derivatives by:: plt.plot(x, p.d(x)) plt.show() which should produce the following figure: .. figure:: images/hermite_der.png :align: center The derivative of the Hermite polynomials up to degree 10. Laguerre polynomials ++++++++++++++++++++ The Laguerre polynomials are defined on :math:`(0, \infty)` are orthogonal with respect to the probability density: .. math:: f(x) = e^{-x}. Up to degree 10, they may be constructed by:: from best.gpc import OrthogonalPolynomial infty = float('inf') # A number representing infinty. degree = 10 # The degree of the polynomials wf = lambda(x): np.exp(-x) p = OrthogonalPolynomial(degree, left=0, right=infty, wf=wf) Here is how they look: .. figure:: images/laguerre.png :align: center The Laguerre polynomials up to degree 10. Exploiting :mod:`scipy.stats` +++++++++++++++++++++++++++++ It is also possible to use functionality from scipy to define the probability density. For example, you may construct the Laguerre polynomials by:: import scipy.stats # Define the random variable rv = scipy.stats.expon() p = OrthogonalPolynomial(degree, rv=rv) This is a nice trick, because you can immediately construct any orthogonal polynomial you wish making use of the probability distributions that can be found in `scipy.stats `_. All you need to do is: 1. Construct a random variable ``rv``. 2. USe ``rv.pdf`` as the weight function when constructing the :class:`best.gpc.OrthogonalPolynomial`. Here are for example orthogonal polynomials with respect to the Beta distribution: .. math:: f(x) = \frac{\Gamma(a + b)}{\Gamma(a)\Gamma(b)} x^{a - 1} (1 - x)^{b - 1}, with :math:`a, b>0` and :math:`x \in (0, 1)`:: import scipy.stats a = 0.3 b = 0.8 rv = scipy.stats.beta(a, b) p = best.gpc.OrthogonalPolynomial(6, rv=rv) Here are the first six: .. figure:: images/beta.png :align: center The first six orthogonal polynomials with respect to the Beta \ distribution with :math:`a = 0.3, b = 0.8`. Exploiting :mod:`best.random` ++++++++++++++++++++++++++++ Let us take the Exponential random variable of the previous example, condition it to live in a small interval (say :math:`(1, 2)`) and construct some orthogonal polynomials there. Here is how:: import scipy.stats from best.random import RandomVariableConditional # Define the random variable rv = scipy.stats.expon() # The conditioned random variable rv_cond = RandomVariableConditional(rv, (1, 2)) p = OrthogonalPolynomial(degree, rv=rv_cond) This produces the following figure: .. figure:: images/expon_conditioned.png :align: center The first six orthogonal polynomials with respect to an Exponential distribution restricted to live on :math:`(1, 2)`. Constructing Product Basis ++++++++++++++++++++++++++ Let's construct now a product basis of degree six based on a random vector of independent random variables. This can be achieved by:: import scipy.stats from best.random import RandomVectorIndependent from best.gpc import ProductBasis # Construct the random vector (exponential, beta and normal) rv = RandomVectorIndependent((scipy.stats.expon(), scipy.stats.beta(0.4, 0.7), scipy.stats.norm())) # Construct the product basis p = ProductBasis(rv=rv, degree=6) print str(p) The output is as follows:: Product basis:R^3 --> R^84 sz = 84 0: 0 0 0 1: 1 0 0 2: 0 1 0 3: 0 0 1 4: 2 0 0 5: 1 1 0 6: 1 0 1 7: 0 2 0 8: 0 1 1 9: 0 0 2 10: 3 0 0 11: 2 1 0 12: 2 0 1 13: 1 2 0 14: 1 1 1 15: 1 0 2 16: 0 3 0 17: 0 2 1 18: 0 1 2 19: 0 0 3 20: 4 0 0 21: 3 1 0 22: 3 0 1 23: 2 2 0 24: 2 1 1 25: 2 0 2 26: 1 3 0 27: 1 2 1 28: 1 1 2 29: 1 0 3 30: 0 4 0 31: 0 3 1 32: 0 2 2 33: 0 1 3 34: 0 0 4 35: 5 0 0 36: 4 1 0 37: 4 0 1 38: 3 2 0 39: 3 1 1 40: 3 0 2 41: 2 3 0 42: 2 2 1 43: 2 1 2 44: 2 0 3 45: 1 4 0 46: 1 3 1 47: 1 2 2 48: 1 1 3 49: 1 0 4 50: 0 5 0 51: 0 4 1 52: 0 3 2 53: 0 2 3 54: 0 1 4 55: 0 0 5 56: 6 0 0 57: 5 1 0 58: 5 0 1 59: 4 2 0 60: 4 1 1 61: 4 0 2 62: 3 3 0 63: 3 2 1 64: 3 1 2 65: 3 0 3 66: 2 4 0 67: 2 3 1 68: 2 2 2 69: 2 1 3 70: 2 0 4 71: 1 5 0 72: 1 4 1 73: 1 3 2 74: 1 2 3 75: 1 1 4 76: 1 0 5 77: 0 6 0 78: 0 5 1 79: 0 4 2 80: 0 3 3 81: 0 2 4 82: 0 1 5 83: 0 0 6 num_terms = 1 4 10 20 35 56 84 Let's evaluate the basis at a few points:: x = rv.rvs(size=10) phi = p(x) print phi.shape where ``phi`` is the design matrix.