Source code for macro_eeg_model.simulation.distributions

# standard imports
from enum import Enum

# external imports
import numpy as np
from scipy.stats import rv_continuous
from sklearn.neighbors import KernelDensity


[docs] class LagDistributions(Enum): """ An enumeration for different types of lag distributions. Attributes ---------- INVERSE_GEV : str Represents an inverse generalized extreme value (GEV) distribution. INVERSE_GEV_SUM : str Represents the sum of two inverse GEV distributions. """ INVERSE_GEV = "Inverse GEV" INVERSE_GEV_SUM = "Sum of Inverse GEVs"
[docs] class DistributionFactory: """ A factory class responsible for creating different types of distributions based on the provided type. """
[docs] @staticmethod def get_distribution(distribution_type, **kwargs): """ Creates and returns a distribution object based on the specified type. Parameters ---------- distribution_type : LagDistributions The type of distribution to create (e.g., INVERSE_GEV, INVERSE_GEV_SUM). kwargs : dict The parameters required to initialize the distribution. Returns ------- rv_continuous An instance of a distribution class (e.g., :py:class:`InverseGEV`, :py:class:`InverseGEVSum`). Raises ------ ValueError If an unknown distribution type is provided. """ if distribution_type == LagDistributions.INVERSE_GEV: lmbd = kwargs["lmbd"] mu = kwargs["mu"] sigma = kwargs["sigma"] xi = kwargs["xi"] return InverseGEV(lmbd=lmbd, mu=mu, sigma=sigma, xi=xi) if distribution_type == LagDistributions.INVERSE_GEV_SUM: lmbd1 = kwargs["lmbd1"] lmbd2 = kwargs["lmbd2"] mu = kwargs["mu"] sigma = kwargs["sigma"] xi = kwargs["xi"] return InverseGEVSum(lmbd1=lmbd1, lmbd2=lmbd2, mu=mu, sigma=sigma, xi=xi) raise ValueError("Unknown distribution type")
[docs] class InverseGEV(rv_continuous): """ A class representing the inverse generalized extreme value (GEV) distribution. This class extends `scipy.stats.rv_continuous` to model the inverse GEV distribution. Attributes ---------- lmbd : float A scaling parameter applied to the distribution. mu : float The location parameter of the GEV distribution. sigma : float The scale parameter of the GEV distribution. xi : float The shape parameter of the GEV distribution. """
[docs] def __init__(self, lmbd, mu, sigma, xi, *args, **kwargs): """ Initializes the InverseGEV distribution with the specified parameters. Parameters ---------- lmbd : float A scaling parameter applied to the distribution. mu : float The location parameter of the GEV distribution. sigma : float The scale parameter of the GEV distribution. xi : float The shape parameter of the GEV distribution. """ super().__init__(*args, **kwargs) self.lmbd = lmbd self.mu = mu # location self.sigma = sigma # scale self.xi = xi # shape
[docs] def _argcheck(self, *args): """ Validates the distribution parameters. Returns ------- bool True if the parameters are valid, False otherwise. """ return bool((self.sigma > 0) and (self.xi != 0))
[docs] def _cdf(self, x, *args): """ Calculates the cumulative distribution function (CDF) for the inverse GEV. Parameters ---------- x : array_like The quantiles at which to evaluate the CDF. Returns ------- array_like The CDF evaluated at the given quantiles. """ def gev_cdf(n): s = (n - self.mu) / self.sigma t = 1 + self.xi * s term = np.power(t, -1 / self.xi) return np.exp(-1 * term) return 1 - gev_cdf(self.lmbd / x)
[docs] def _pdf(self, x, *args): """ Calculates the probability density function (PDF) for the inverse GEV. Parameters ---------- x : array_like The quantiles at which to evaluate the PDF. Returns ------- array_like The PDF evaluated at the given quantiles. """ t = 1 + self.xi * ((self.lmbd - self.mu * x) / (self.sigma * x)) t = np.where(t < 0, 0, t) t = np.array([ 0 if ((ti == 0) and (-1 / self.xi < 0)) else np.power(ti, -1 / self.xi) for ti in t ]) term1 = np.array([ 0 if ((ti == 0) and (self.xi + 1 < 0)) else np.power(ti, self.xi + 1) for ti in t ]) term2 = np.exp(-1 * t) pdf = (self.lmbd / np.power(x, 2)) * (1 / self.sigma) * term1 * term2 pdf = np.where(pdf < 0, 0, pdf) return pdf
[docs] def _ppf(self, q, *args): """ Calculates the percent point function (PPF), also known as the quantile function, for the inverse GEV. Parameters ---------- q : array_like The quantiles for which to evaluate the PPF. Returns ------- array_like The PPF evaluated at the given quantiles. """ # Inverse CDF (quantile function) def gev_ppf(p): term = -np.log(p) t = np.power(term, -self.xi) s = (t - 1) / self.xi return self.mu + self.sigma * s return self.lmbd / gev_ppf(1 - q)
[docs] def _rvs(self, *args, size=None, random_state=None): """ Generates random variates from the inverse GEV distribution. Parameters ---------- size : int or tuple of ints, optional The number of random variates to generate. random_state : np.random.RandomState, optional A random state instance for reproducibility. Returns ------- array_like The generated random variates. """ u = self._random_state.random_sample(size) return self._ppf(u)
[docs] class InverseGEVSum(rv_continuous): """ A class representing the sum of two inverse GEV distributions. This class extends `scipy.stats.rv_continuous` to model the sum of two inverse GEV distributions. The sum is approximated using a kernel density estimate (KDE) of the sum of samples from the two distributions. Attributes ---------- lmbd1 : float A scaling parameter for the first inverse GEV distribution. lmbd2 : float A scaling parameter for the second inverse GEV distribution. mu : float The location parameter of the GEV distributions. sigma : float The scale parameter of the GEV distributions. xi : float The shape parameter of the GEV distributions. _kde : KernelDensity The kernel density estimate of the sum of the two inverse GEV distributions. """
[docs] def __init__(self, lmbd1, lmbd2, mu, sigma, xi, *args, **kwargs): """ Initializes the InverseGEVSum distribution with the specified parameters. Parameters ---------- lmbd1 : float A scaling parameter for the first inverse GEV distribution. lmbd2 : float A scaling parameter for the second inverse GEV distribution. mu : float The location parameter of the GEV distributions. sigma : float The scale parameter of the GEV distributions. xi : float The shape parameter of the GEV distributions. """ super().__init__(*args, **kwargs) self.lmbd1 = lmbd1 self.lmbd2 = lmbd2 self.mu = mu self.sigma = sigma self.xi = xi self._kde = self._get_kde()
[docs] def _get_kde(self): """ Generates a kernel density estimate (KDE) for the sum of two inverse GEV distributions. Returns ------- KernelDensity A KDE fitted to the sum of samples from the two inverse GEV distributions. """ inverse_gev1 = InverseGEV(lmbd=self.lmbd1, mu=self.mu, sigma=self.sigma, xi=self.xi) inverse_gev2 = InverseGEV(lmbd=self.lmbd2, mu=self.mu, sigma=self.sigma, xi=self.xi) # generate samples for 1 and 2 samples1 = inverse_gev1.rvs(size=1000) samples2 = inverse_gev2.rvs(size=1000) samples = samples1 + samples2 silverman_bandwidth = (4 * np.std(samples) ** 5 / (3 * len(samples))) ** (1 / 5) return KernelDensity(kernel='gaussian', bandwidth=silverman_bandwidth).fit(samples[:, np.newaxis])
[docs] def _argcheck(self, *args): """ Validates the distribution parameters. Returns ------- bool True if the parameters are valid, False otherwise. """ return bool((self.sigma > 0) and (self.xi != 0))
[docs] def _pdf(self, x, *args): """ Evaluates the kernel density estimate (KDE) at the given quantiles to approximate the probability density function (PDF) for the sum of inverse GEVs. Parameters ---------- x : array_like The quantiles at which to evaluate the PDF. Returns ------- array_like The PDF evaluated at the given quantiles. """ return np.exp(self._kde.score_samples(x[:, np.newaxis]))