import scipy.stats as stats
from beartype import beartype
from UQpy.utilities.ValidationTypes import *
from UQpy.utilities.Utilities import process_random_state
[docs]class SimplexSampling:
@beartype
def __init__(
self,
nodes: Union[list, Numpy2DFloatArray] = None,
nsamples: PositiveInteger = None,
random_state: Union[RandomStateType, np.random.Generator] = None,
):
"""
Generate uniform random samples inside an n-dimensional simplex.
:param nodes: The vertices of the simplex.
:param nsamples: The number of samples to be generated inside the simplex.
If `nsamples` is provided when the object is defined, the :meth:`run` method will be called
automatically. If `nsamples` is not provided when the object is defined, the user must invoke the
:meth:`run` method and specify `nsamples`.
:param random_state: Random seed used to initialize the pseudo-random number generator. Default is :any:`None`.
If an :any:`int` is provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise,
the object itself can be passed directly.
"""
self.samples: NumpyFloatArray = None
self.nodes = np.atleast_2d(nodes)
self.nsamples = nsamples
if self.nodes.shape[0] != self.nodes.shape[1] + 1:
raise NotImplementedError("UQpy: Size of simplex (nodes) is not consistent.")
self.random_state = process_random_state(random_state)
if nsamples is not None:
self.run(nsamples=nsamples)
"""New random samples distributed uniformly inside the simplex."""
[docs] @beartype
def run(self, nsamples: PositiveInteger):
"""
Execute the random sampling in the :class:`.SimplexSampling` class.
The :meth:`run` method is the function that performs random sampling in the :class:`.SimplexSampling` class.
If `nsamples` is provided called when the :class:`.SimplexSampling` object is defined, the
:meth:`run` method is automatically. The user may also call the :meth:`run` method directly to generate samples.
The :meth:`run` method of the :class:`.SimplexSampling` class can be
invoked many times and each time the generated samples are appended to the existing samples.
:param nsamples: Number of samples to be generated inside the simplex.
If the :meth:`run` method is invoked multiple times, the newly generated samples will be appended to the
existing samples.
:return: The :meth:`run` method has no returns, although it creates and/or appends the :py:attr:`samples`
attribute of the :class:`.SimplexSampling` class.
"""
self.nsamples = nsamples
dimension = self.nodes.shape[1]
if dimension > 1:
sample = np.zeros([self.nsamples, dimension])
for i in range(self.nsamples):
r = np.zeros([dimension])
ad = np.zeros(shape=(dimension, len(self.nodes)))
for j in range(dimension):
b_ = []
for k in range(1, len(self.nodes)):
ai = self.nodes[k, j] - self.nodes[k - 1, j]
b_.append(ai)
ad[j] = np.hstack((self.nodes[0, j], b_))
r[j] = stats.uniform.rvs(loc=0, scale=1, random_state=self.random_state) ** (1 / (dimension - j))
d = np.cumprod(r)
r_ = np.hstack((1, d))
sample[i, :] = np.dot(ad, r_)
else:
a = min(self.nodes)
b = max(self.nodes)
sample = a + (b - a) * stats.uniform.rvs(size=[self.nsamples, dimension], random_state=self.random_state)
self.samples: NumpyFloatArray = sample