"Definition of a univariate parameter."
from equadratures.distributions.gaussian import Gaussian
from equadratures.distributions.uniform import Uniform
from equadratures.distributions.triangular import Triangular
from equadratures.distributions.chebyshev import Chebyshev
from equadratures.distributions.beta import Beta
from equadratures.distributions.cauchy import Cauchy
from equadratures.distributions.exponential import Exponential
from equadratures.distributions.gamma import Gamma
from equadratures.distributions.weibull import Weibull
from equadratures.distributions.rayleigh import Rayleigh
from equadratures.distributions.chisquared import Chisquared
from equadratures.distributions.truncated_gaussian import TruncatedGaussian
from equadratures.distributions.pareto import Pareto
from equadratures.distributions.lognormal import Lognormal
from equadratures.distributions.studentst import Studentst
from equadratures.distributions.logistic import Logistic
from equadratures.distributions.gumbel import Gumbel
from equadratures.distributions.chi import Chi
from equadratures.distributions.analytical import Analytical
import equadratures.plot as plot
import numpy as np
import scipy as sc
[docs]class Parameter(object):
""" This class defines a univariate parameter.
Parameters
----------
lower : float, optional
Lower bound for the parameter.
upper : float, optional
Upper bound for the parameter.
order : int, optional
Order of the parameter.
param_type : str, optional
The type of distribution that characterizes the parameter (see [1, 2]). Options include `chebyshev (arcsine) <https://en.wikipedia.org/wiki/Arcsine_distribution>`_, `gaussian <https://en.wikipedia.org/wiki/Normal_distribution>`_,
`truncated-gaussian <https://en.wikipedia.org/wiki/Truncated_normal_distribution>`_, `beta <https://en.wikipedia.org/wiki/Beta_distribution>`_,
`cauchy <https://en.wikipedia.org/wiki/Cauchy_distribution>`_, `exponential <https://en.wikipedia.org/wiki/Exponential_distribution>`_,
`uniform <https://en.wikipedia.org/wiki/Uniform_distribution_(continuous)>`_, `triangular <https://en.wikipedia.org/wiki/Triangular_distribution>`_, `gamma <https://en.wikipedia.org/wiki/Gamma_distribution>`_,
`weibull <https://en.wikipedia.org/wiki/Weibull_distribution>`_, `rayleigh <https://en.wikipedia.org/wiki/Rayleigh_distribution>`_,
`pareto <https://en.wikipedia.org/wiki/Pareto_distribution>`_, `lognormal <https://en.wikipedia.org/wiki/Log-normal_distribution>`_,
`students-t <https://en.wikipedia.org/wiki/Student%27s_t-distribution>`_, `logistic <https://en.wikipedia.org/wiki/Log-normal_distribution>`_,
`gumbel <https://en.wikipedia.org/wiki/Gumbel_distribution>`_, `chi <https://en.wikipedia.org/wiki/Chi_distribution>`_ and `chi-squared <https://en.wikipedia.org/wiki/Chi-squared_distribution>`_.
If no string is provided, a ``uniform`` distribution is assumed. Data-driven and custom analytical parameters can also be constructed by setting this option to ``data`` and ``analytical`` and providing a **weight_function** (see examples).
shape_parameter_A : float, optional
Most of the aforementioned distributions are characterized by two shape parameters. For instance, in the case of a ``gaussian`` (or ``truncated-gaussian``), this represents the mean. In the case of a beta distribution this represents the alpha value. For a ``uniform`` distribution this input is not required.
shape_parameter_B : float, optional
This is the second shape parameter that characterizes the distribution selected. In the case of a ``gaussian`` or ``truncated-gaussian``, this is the variance.
data : numpy.ndarray, optional
A data-set with shape (number_of_data_points, 2), where the first column comprises of parameter values, while the second column corresponds to the data observations. This input should only be used with the ``Analytical`` distribution.
endpoints : str, optional
If set to ``both``, then the quadrature points and weights will have end-points, based on Gauss-Lobatto quadrature rules. If set to ``upper`` or ``lower`` a Gauss-Radau rule is used to compute one end-point at either the upper or lower bound.
weight_function: Weight, optional
An instance of Weight, which contains a bespoke analytical or data-driven weight (probability density) function.
Examples
--------
A uniform parameter
>>> param = eq.Parameter(distribution='uniform', lower=-2, upper=2., order=3)
A beta parameter
>>> param = eq.Parameter(distribution='beta', lower=-2., upper=15., order=4,
>>> shape_parameter_A=3.2, shape_parameter_B=1.7)
A data-driven parameter
>>> pdf = eq.Weight( stats.gaussian_kde(data, bw_method='silverman'),
>>> support=[-3, 3.2])
>>> param = eq.Parameter(distribution='analytical',
>>> weight_function=pdf, order=2)
References
----------
1. Xiu, D., Karniadakis, G. E., (2002) The Wiener-Askey Polynomial Chaos for Stochastic Differential Equations. SIAM Journal on Scientific Computing, 24(2), `Paper <https://epubs.siam.org/doi/abs/10.1137/S1064827501387826?journalCode=sjoce3>`__
2. Gautschi, W., (1985) Orthogonal Polynomials-Constructive Theory and Applications. Journal of Computational and Applied Mathematics 12 (1985), pp. 61-76. `Paper <https://www.sciencedirect.com/science/article/pii/037704278590007X>`__
"""
def __init__(self, order=1, distribution='Uniform', endpoints=None, shape_parameter_A=None, shape_parameter_B=None, variable='parameter', lower=None, upper=None, weight_function=None):
self.name = distribution
self.variable = variable
self.order = order
self.shape_parameter_A = shape_parameter_A
self.shape_parameter_B = shape_parameter_B
self.lower = lower
self.upper = upper
self.endpoints = endpoints
self.weight_function = weight_function
self._set_distribution()
self._set_bounds()
self._set_moments()
if self.endpoints is not None:
if (self.distribution.bounds[0] == -np.inf) and (self.distribution.bounds[1] == np.inf) and (self.endpoints.lower() == 'both'):
raise(ValueError, 'Parameter: The lower bound for your distribution is -infinity and the upper bound is infinity. Furthermore, you have selected the to have both endpoints. These options are incompatible!')
if (self.distribution.bounds[0] == -np.inf) and (self.endpoints.lower() == 'lower'):
raise(ValueError, 'Parameter: The lower bound for your distribution is -infinity and you have selected the lower bound option in the endpoints. These options are incompatible!')
if (self.distribution.bounds[1] == np.inf) and (self.endpoints.lower() == 'upper'):
raise(ValueError, 'Parameter: The upper bound for your distribution is infinity and you have selected the upper bound option in the endpoints. These options are incompatible!')
def _set_distribution(self):
""" Private function that sets the distribution. """
if self.name.lower() == 'gaussian' or self.name.lower() == 'normal':
self.distribution = Gaussian(self.shape_parameter_A, self.shape_parameter_B)
elif self.name.lower() == 'uniform':
self.distribution = Uniform(self.lower, self.upper)
elif self.name.lower() == 'triangular':
self.distribution = Triangular(self.lower, self.upper, self.shape_parameter_A)
elif self.name.lower() == 'analytical' or self.name.lower() == 'data':
self.distribution = Analytical(self.weight_function)
elif self.name.lower() == 'beta':
self.distribution = Beta(self.lower, self.upper, self.shape_parameter_A, self.shape_parameter_B)
elif self.name.lower() == 'truncated-gaussian':
self.distribution = TruncatedGaussian(self.shape_parameter_A, self.shape_parameter_B, self.lower, self.upper)
elif self.name.lower() == 'cauchy':
self.distribution = Cauchy(self.shape_parameter_A, self.shape_parameter_B)
elif self.name.lower() == 'exponential':
self.distribution = Exponential(self.shape_parameter_A)
elif self.name.lower() == 'gamma':
self.distribution = Gamma(self.shape_parameter_A, self.shape_parameter_B)
elif self.name.lower() == 'weibull':
self.distribution = Weibull(self.shape_parameter_A, self.shape_parameter_B)
elif self.name.lower() == 'arcsine' or self.name.lower() == 'chebyshev':
self.distribution = Chebyshev(self.lower, self.upper)
elif self.name.lower() == 'rayleigh':
self.distribution = Rayleigh(self.shape_parameter_A)
elif self.name.lower() == 'chi-squared':
self.distribution = Chisquared(self.shape_parameter_A)
elif self.name.lower() == 'chi':
self.distribution = Chi(self.shape_parameter_A)
elif self.name.lower() == 'pareto':
self.distribution = Pareto(self.shape_parameter_A)
elif self.name.lower() == 'gumbel':
self.distribution = Gumbel(self.shape_parameter_A, self.shape_parameter_B)
elif self.name.lower() == 'logistic':
self.distribution = Logistic(self.shape_parameter_A, self.shape_parameter_B)
elif self.name.lower() == 'students-t' or self.name.lower() == 't' or self.name.lower() == 'studentt':
self.distribution = Studentst(self.shape_parameter_A)
elif self.name.lower() == 'lognormal' or self.name.lower() == 'log-normal':
self.distribution = Lognormal(self.shape_parameter_A)
else:
distribution_error()
self.mean = self.distribution.mean
self.variance = self.distribution.variance
[docs] def plot_orthogonal_polynomials(self, ax=None, order_limit=None, number_of_points=200, show=True):
""" Plots the first few orthogonal polynomials. See :meth:`~equadratures.plot.plot_orthogonal_polynomials` for full description. """
return plot.plot_orthogonal_polynomials(self,ax,order_limit,number_of_points,show)
[docs] def plot_pdf(self, ax=None, data=None, show=True, lim_range=True):
""" Plots the probability density function for a Parameter. See :meth:`~equadratures.plot.plot_pdf` for full description. """
return plot.plot_pdf(self,ax, data, show, lim_range)
[docs] def plot_cdf(self, ax=None, show=True, lim_range=True):
""" Plots the cumulative density function for a Parameter. See :meth:`~equadratures.plot.plot_cdf` for full description. """
return plot.plot_cdf(self,ax, show, lim_range)
def _set_moments(self):
""" Private function that sets the mean and the variance of the distribution. """
self.mean = self.distribution.mean
self.variance = self.distribution.variance
def _set_bounds(self):
""" Private function that sets the bounds of the distribution. """
self.bounds = self.distribution.bounds
[docs] def get_pdf(self, points=None):
""" Computes the probability density function associated with the Parameter.
Parameters
----------
points : numpy.ndarray, optional
Values of the parameter at which the PDF must be evaluated.
Returns
-------
numpy.ndarray
If ``points!=None``. ndarray containing the probability density function evaluated at the points in ``points``.
tuple
If ``points=None``. A tuple (`x`, `pdf`), where `pdf` is the probability density function evaluated at the points in `x`.
"""
if points is None:
x = self.distribution.x_range_for_pdf
return x, self.distribution.get_pdf(x)
else:
return self.distribution.get_pdf(points)
[docs] def get_cdf(self, points=None):
""" Computes the cumulative density function associated with the Parameter.
Parameters
----------
points : numpy.ndarray, optional
Values of the parameter at which the CDF must be evaluated.
Returns
-------
numpy.ndarray
If ``points!=None``. ndarray containing the cumulative density function evaluated at the points in ``points``.
tuple
If ``points=None``. A tuple (`x`, `cdf`), where `cdf` is the cumulative density function evaluated at the points in `x`.
"""
if points is None:
x = self.distribution.x_range_for_pdf
return x, self.distribution.get_cdf(x)
else:
return self.distribution.get_cdf(points)
[docs] def get_icdf(self, cdf_values):
""" Computes the inverse cumulative density function associated with the Parameter.
Parameters
----------
cdf_values : numpy.ndarray
Values of the cumulative density function for which its inverse needs to be computed.
Returns
-------
numpy.ndarray
The inverse cumulative density function.
"""
return self.distribution.get_icdf(cdf_values)
[docs] def get_samples(self, number_of_samples_required):
""" Generates samples from the distribution associated with the Parameter.
Parameters
----------
number_of_samples_required : int
Number of samples that are required.
Returns
-------
numpy.ndarray
The generated samples.
"""
return self.distribution.get_samples(number_of_samples_required)
[docs] def get_description(self):
""" Provides a description of the Parameter.
Returns
-------
str
A description of the parameter.
"""
return self.distribution.get_description()
[docs] def get_recurrence_coefficients(self, order=None):
""" Generates the recurrence coefficients.
Parameters
----------
order : int, optional
Order of the recurrence coefficients.
Returns
-------
numpy.ndarray
Array of recurrence coefficients.
"""
return self.distribution.get_recurrence_coefficients(order)
[docs] def get_jacobi_eigenvectors(self, order=None):
""" Computes the eigenvectors of the Jacobi matrix.
Parameters
----------
order : int
Order of the recurrence coefficients.
Returns
-------
numpy.ndarray
Array of eigenvectors.
"""
if order is None:
order = self.order + 1
JacobiMat = self.get_jacobi_matrix(order)
if order == 1:
V = [1.0]
else:
#D,V = np.linalg.eig(self.get_jacobi_matrix(order))
D, V = sc.linalg.eigh(self.get_jacobi_matrix(order))
idx = D.argsort()[::-1]
eigs = D[idx]
eigVecs = V[:, idx]
#V = np.mat(V) # convert to matrix
#i = np.argsort(D) # get the sorted indices
#i = np.array(i) # convert to array
#V = V[:,i]
return eigVecs
[docs] def get_jacobi_matrix(self, order=None, ab=None):
""" Computes the Jacobi matrix---a tridiagonal matrix of the recurrence coefficients.
Parameters
----------
order : int
Order of the recurrence coefficients.
Returns
-------
numpy.ndarray
2D array containing the Jacobi matrix.
"""
if order is None and ab is None:
ab = self.get_recurrence_coefficients()
order = self.order + 1
elif ab is None:
ab = self.get_recurrence_coefficients(order)
else:
ab = ab[0:order, :]
order = int(order)
# The case of order 1~
if int(order) == 1:
JacobiMatrix = ab[0, 0]
# For everything else~
else:
JacobiMatrix = np.zeros((int(order), int(order))) # allocate space
JacobiMatrix[0,0] = ab[0,0]
JacobiMatrix[0,1] = np.sqrt(ab[1,1])
k = order - 1
for u in range(1, int(k)):
JacobiMatrix[u,u] = ab[u,0]
JacobiMatrix[u,u-1] = np.sqrt(ab[u,1])
JacobiMatrix[u,u+1] = np.sqrt(ab[u+1,1])
JacobiMatrix[order-1, order-1] = ab[order-1,0]
JacobiMatrix[order-1, order-2] = np.sqrt(ab[order-1,1])
return JacobiMatrix
def _get_orthogonal_polynomial(self, points, order=None):
"""
Private function that evaluates the univariate orthogonal polynomial at quadrature points.
:param Parameter self:
An instance of the Parameter object.
:param numpy.ndarray points:
Points at which the orthogonal polynomial must be evaluated.
:param int order:
Order up to which the orthogonal polynomial must be obtained.
"""
if order is None:
order = self.order + 1
else:
order = order + 1
gridPoints = np.asarray(points).copy()
ab = self.get_recurrence_coefficients(order)
"""
print('Before:')
print(gridPoints)
for q in range(0, gridPoints.shape[0]):
if (gridPoints[q] < self.bounds[0]) or (gridPoints[q] > self.bounds[1]):
grid_flag = 1
if grid_flag == 1:
for r in range(0, gridPoints.shape[0]):
gridPoints[r] = (self.bounds[1] - self.bounds[0]) * ( (gridPoints[r] - self.lower) / (self.upper - self.lower) ) + self.bounds[0]
#print(gridPoints)
print('After:')
print(gridPoints)
"""
orthopoly = np.zeros((order, len(gridPoints))) # create a matrix full of zeros
derivative_orthopoly = np.zeros((order, len(gridPoints)))
dderivative_orthopoly = np.zeros((order, len(gridPoints)))
# Convert the grid points to a numpy array -- simplfy life!
gridPointsII = gridPoints.reshape((-1, 1))
orthopoly[0, :] = 1.0
# Cases
if order == 1: # CHANGED 2/2/18
return orthopoly, derivative_orthopoly, dderivative_orthopoly
orthopoly[1, :] = ((gridPointsII[:, 0] - ab[0, 0]) * orthopoly[0, :]) * (1.0) / (1.0 * np.sqrt(ab[1, 1]))
derivative_orthopoly[1, :] = orthopoly[0, :] / (np.sqrt(ab[1, 1]))
if order == 2: # CHANGED 2/2/18
return orthopoly, derivative_orthopoly, dderivative_orthopoly
if order >= 3: # CHANGED 2/2/18
for u in range(2, order): # CHANGED 2/2/18
# Three-term recurrence rule in action!
orthopoly[u, :] = (((gridPointsII[:, 0] - ab[u - 1, 0]) * orthopoly[u - 1, :]) - np.sqrt(
ab[u - 1, 1]) * orthopoly[u - 2, :]) / (1.0 * np.sqrt(ab[u, 1]))
for u in range(2, order): # CHANGED 2/2/18
# Four-term recurrence formula for derivatives of orthogonal polynomials!
derivative_orthopoly[u,:] = ( ((gridPointsII[:,0] - ab[u-1,0]) * derivative_orthopoly[u-1,:]) - ( np.sqrt(ab[u-1,1]) * derivative_orthopoly[u-2,:] ) + orthopoly[u-1,:] )/(1.0 * np.sqrt(ab[u,1]))
for u in range(2,order):
# Four-term recurrence formula for second derivatives of orthogonal polynomials!
dderivative_orthopoly[u,:] = ( ((gridPointsII[:,0] - ab[u-1,0]) * dderivative_orthopoly[u-1,:]) - ( np.sqrt(ab[u-1,1]) * dderivative_orthopoly[u-2,:] ) + 2.0 * derivative_orthopoly[u-1,:] )/(1.0 * np.sqrt(ab[u,1]))
return orthopoly, derivative_orthopoly, dderivative_orthopoly
def _get_local_quadrature(self, order=None, ab=None):
"""
Returns the 1D quadrature points and weights for the parameter. WARNING: Should not be called under normal circumstances.
:param Parameter self:
An instance of the Parameter class
:param int N:
Number of quadrature points and weights required. If order is not specified, then by default the method will return the number of points defined in the parameter itself.
:return:
A N-by-1 matrix that contains the quadrature points
:return:
A 1-by-N matrix that contains the quadrature weights
"""
if self.endpoints is None:
return get_local_quadrature(self, order, ab)
elif self.endpoints.lower() == 'lower' or self.endpoints.lower() == 'upper':
return get_local_quadrature_radau(self, order, ab)
elif self.endpoints.lower() == 'both':
return get_local_quadrature_lobatto(self, order, ab)
else:
raise(ValueError, 'Error in endpoints specification.')
def get_local_quadrature(self, order=None, ab=None):
# Check for extra input argument!
if order is None:
order = self.order + 1
else:
order = order + 1
if ab is None:
# Get the recurrence coefficients & the jacobi matrix
JacobiMat = self.get_jacobi_matrix(order)
ab = self.get_recurrence_coefficients(order+1)
else:
ab = ab[0:order+1,:]
JacobiMat = self.get_jacobi_matrix(order, ab)
# If statement to handle the case where order = 1
if order == 1:
# Check to see whether upper and lower bound are defined:
if not self.lower or not self.upper:
p = np.asarray(self.distribution.mean).reshape((1,1))
else:
p = np.asarray((self.upper - self.lower)/(2.0) + self.lower).reshape((1,1))
w = [1.0]
else:
# Compute eigenvalues & eigenvectors of Jacobi matrix
#D,V = np.linalg.eig(JacobiMat)
D, V = sc.linalg.eigh(JacobiMat)
#V = np.mat(V) # convert to matrix
#local_points = np.sort(D) # sort by the eigenvalues
#i = np.argsort(D) # get the sorted indices
#i = np.array(i) # convert to array
idx = D.argsort()[::-1]
eigs = D[idx]
eigVecs = V[:, idx]
w = np.linspace(1,order+1,order) # create space for weights
p = np.ones((int(order),1))
for u in range(0, len(idx) ):
w[u] = float(ab[0,1]) * (eigVecs[0,idx[u]]**2) # replace weights with right value
p[u,0] = eigs[u]
#if (p[u,0] < 1e-16) and (-1e-16 < p[u,0]):
# p[u,0] = np.abs(p[u,0])
p = p[::-1]
return p, w
def get_local_quadrature_radau(self, order=None, ab=None):
if self.endpoints.lower() == 'lower':
end0 = self.lower
elif self.endpoints.lower() == 'upper':
end0 = self.upper
if order is None:
order = self.order - 1
else:
order = order - 1
N = order
if ab is None:
ab = self.get_recurrence_coefficients(order+1)
else:
ab = ab[0:order+1, :]
p0 = 0.
p1 = 1.
for i in range(0, N+1):
pm1 = p0
p0 = p1
p1 = (end0 - ab[i, 0]) * p0 - ab[i, 1]*pm1
ab[N+1, 0] = end0 - ab[N+1, 1] * p0/p1
return get_local_quadrature(self, order=order+1, ab=ab)
def get_local_quadrature_lobatto(self, order=None, ab=None):
if order is None:
order = self.order - 2
else:
order = order - 2
N = order
endl = self.lower
endr = self.upper
if ab is None:
ab = self.get_recurrence_coefficients(order+2)
else:
ab = ab[0:order+2, :]
p0l = 0.
p0r = 0.
p1l = 1.
p1r = 1.
for i in range(0, N+2):
pm1l = p0l
p0l = p1l
pm1r = p0r
p0r = p1r
p1l = (endl - ab[i, 0]) * p0l - ab[i, 1] * pm1l
p1r = (endr - ab[i, 0]) * p0r - ab[i, 1] * pm1r
det = p1l * p0r - p1r * p0l
ab[N+2, 0] = (endl*p1l*p0r-endr*p1r*p0l)/det
ab[N+2, 1] = (endr - endl) * p1l * p1r/det
return get_local_quadrature(self, order=order+2, ab=ab)
def distribution_error():
raise(ValueError, 'Please select a valid distribution for your parameter; documentation can be found at www.effective-quadratures.org')