Skip to content

Commit 3073b3c

Browse files
authored
feat: add new distributions: cauchy, beta, uniform and pareto I type (#33)
1 parent 3b93328 commit 3073b3c

File tree

10 files changed

+1489
-0
lines changed

10 files changed

+1489
-0
lines changed

mpest/models/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@
77
)
88
from mpest.models.exponential import ExponentialModel
99
from mpest.models.gaussian import GaussianModel
10+
from mpest.models.uniform import Uniform
1011
from mpest.models.weibull import WeibullModelExp
12+
from mpest.models.cauchy import Cauchy
13+
from mpest.models.pareto import Pareto
14+
from mpest.models.beta import Beta
1115

1216
ALL_MODELS: dict[str, type[AModel]] = {
1317
GaussianModel().name: GaussianModel,
1418
WeibullModelExp().name: WeibullModelExp,
1519
ExponentialModel().name: ExponentialModel,
20+
Cauchy().name: Cauchy,
21+
Pareto().name: Pareto,
22+
Beta().name: Beta,
23+
Uniform().name: Uniform,
1624
}

mpest/models/beta.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import numpy as np
2+
from scipy.special import betaln, digamma, expm1
3+
from scipy.stats import beta
4+
5+
from mpest.annotations import Params, Samples
6+
from mpest.models import AModelDifferentiable, AModelWithGenerator
7+
8+
9+
class Beta(AModelWithGenerator, AModelDifferentiable):
10+
r"""Beta probability distribution model.
11+
12+
This class implements the Beta distribution, a continuous probability distribution
13+
defined on the interval [0, 1].
14+
15+
It inherits functionality for sample generation from AModelWithGenerator
16+
and for computing derivatives from AModelDifferentiable.
17+
18+
The Beta distribution is parameterized by two positive shape parameters:
19+
20+
- External parameters: :math:`a` and :math:`b` (:math:`a,b > 0`)
21+
- Internal (model) parameters: :math:`\mu = \log(a)` and :math:`\nu = \log(b)`
22+
23+
The probability density function (PDF) with external parameters is:
24+
25+
.. math::
26+
27+
f(x; a, b) = \frac{x^{a-1} (1-x)^{b-1}}{B(a, b)}
28+
29+
where :math:`B(a, b) = \frac{\Gamma(a)\Gamma(b)}{\Gamma(a+b)}` is the Beta function,
30+
and :math:`0 < x < 1`.
31+
32+
The PDF with internal parameters is:
33+
34+
.. math::
35+
36+
f(x; \mu, \nu) = \exp\left( (e^\\mu - 1)\log(x) + (e^\nu - 1)\log(1-x)
37+
- \mathrm{betaln}(e^\mu, e^\nu) \right)
38+
"""
39+
40+
@property
41+
def name(self) -> str:
42+
"""Returns the name of the distribution.
43+
44+
Returns:
45+
str: The name of the distribution "Beta".
46+
"""
47+
48+
return "Beta"
49+
50+
def params_convert_to_model(self, params: Params) -> Params:
51+
"""Converts external parameters to internal model parameters.
52+
53+
Transforms the external parameters [:math:`a, b`] to internal model parameters
54+
[:math:`\\mu, \\nu`] where :math:`\\mu = \\log(a)` and :math:`\\nu = \\log(b)`.
55+
56+
Args:
57+
params (Params): External parameters [:math:`a, b`] where :math:`a` and :math:`b`
58+
are the shape parameters of the Beta distribution (:math:`a,b > 0`).
59+
60+
Returns:
61+
Params: Internal model parameters [:math:`\\log(a), \\log(b)`].
62+
"""
63+
64+
return np.log(params)
65+
66+
def params_convert_from_model(self, params: Params) -> Params:
67+
r"""Converts internal model parameters to external parameters.
68+
69+
Transforms the internal model parameters [:math:`\mu, \nu`] to external
70+
parameters [:math:`a, b`] where :math:`a = e^{\mu}` and :math:`b = e^{\nu}`.
71+
72+
Args:
73+
params (Params): Internal model parameters [:math:`\mu, \nu`] where
74+
:math:`\mu` and :math:`\nu` are the log-shape parameters.
75+
76+
Returns:
77+
Params: External parameters [:math:`e^{\mu}, e^{\nu}`].
78+
"""
79+
80+
return np.exp(params)
81+
82+
def generate(self, params: Params, size: int = 1, normalized: bool = True) -> Samples:
83+
r"""Generates random samples from the Beta distribution.
84+
85+
Args:
86+
params (Params): Internal model parameters [:math:`\mu, \nu`] when normalized=True,
87+
or external parameters [:math:`a, b`] when normalized=False.
88+
size (int, optional): Number of samples to generate. Defaults to 1.
89+
normalized (bool, optional): Whether the provided parameters are in normalized
90+
(internal) form. If False, params are treated as external parameters.
91+
Defaults to True.
92+
93+
Returns:
94+
Samples: An array of randomly generated samples from the Beta distribution.
95+
"""
96+
97+
if not normalized:
98+
return np.array(beta.rvs(a=params[0], b=params[1], size=size))
99+
100+
a, b = self.params_convert_from_model(params)
101+
return np.array(beta.rvs(a=a, b=b, size=size))
102+
103+
def pdf(self, x: float, params: Params) -> float:
104+
"""Calculates the probability density function (PDF) value at :math:`x`.
105+
106+
The PDF value is computed by exponentiating the result of the log-PDF function.
107+
108+
Args:
109+
x (float): The point at which to evaluate the PDF (should be in (0, 1)).
110+
params (Params): Internal model parameters [:math:`\\mu, \\nu`] where :math:`\\mu`
111+
and :math:`\\nu` are the log-shape parameters.
112+
113+
Returns:
114+
float: The PDF value at point :math:`x`.
115+
"""
116+
117+
return np.exp(self.lpdf(x, params))
118+
119+
def lpdf(self, x: float, params: Params) -> float:
120+
r"""Calculates the natural logarithm of the probability density function at :math:`x`.
121+
122+
Computes the log-PDF using the formula:
123+
124+
.. math::
125+
126+
\ln(f(x; \mu, \nu)) = (e^\mu - 1)\log(x) + (e^\nu - 1)\log(1-x)
127+
- \mathrm{betaln}(e^\mu, e^\nu)
128+
129+
where :math:`0 < x < 1`.
130+
131+
Args:
132+
x (float): The point at which to evaluate the log-PDF (should be in (0, 1)).
133+
params (Params): Internal model parameters [:math:`\mu, \nu`] where :math:`\mu`
134+
and :math:`\nu` are the log-shape parameters.
135+
136+
Returns:
137+
float: The logarithm of the PDF value at point :math:`x`, or :math:`-\infty` if
138+
:math:`x` is not in (0, 1).
139+
"""
140+
141+
if not (0 < x < 1):
142+
return -np.inf
143+
144+
mu, nu = params
145+
lbeta = -betaln(np.exp(params[0]), np.exp(params[1]))
146+
log1 = expm1(mu) * np.log(x)
147+
log2 = expm1(nu) * np.log(1 - x)
148+
149+
return lbeta + log1 + log2
150+
151+
def ldmu(self, x: float, params: Params) -> float:
152+
r"""Calculates the partial derivative of the log-PDF with respect to :math:`\mu`.
153+
154+
Computes the derivative:
155+
156+
.. math::
157+
158+
\frac{\partial\ln(f(x; \mu, \nu))}{\partial \mu} =
159+
e^\mu (\psi(e^\mu + e^\nu) - \psi(e^\mu) + \log(x))
160+
161+
where :math:`\psi` is the digamma function and :math:`0 < x < 1`.
162+
163+
Args:
164+
x (float): The point at which to evaluate the derivative (should be in (0, 1)).
165+
params (Params): Internal model parameters [:math:`\mu, \nu`] where :math:`\mu`
166+
and :math:`\nu` are the log-shape parameters.
167+
168+
Returns:
169+
float: The partial derivative of the log-PDF with respect to :math:`\mu` at
170+
point :math:`x`, or :math:`-\infty` if :math:`x` is not in (0, 1).
171+
"""
172+
173+
if not (0 < x < 1):
174+
return -np.inf
175+
176+
a, b = self.params_convert_from_model(params)
177+
return a * (digamma(a + b) - digamma(a) + np.log(x))
178+
179+
def ldnu(self, x: float, params: Params) -> float:
180+
r"""Calculates the partial derivative of the log-PDF with respect to :math:`\nu`.
181+
182+
Computes the derivative:
183+
184+
.. math::
185+
186+
\frac{\partial\ln(f(x; \mu, \nu))}{\partial \nu} =
187+
e^\nu (\psi(e^\mu + e^\nu) - \psi(e^\nu) + \log(1-x))
188+
189+
where :math:`\psi` is the digamma function and :math:`0 < x < 1`.
190+
191+
Args:
192+
x (float): The point at which to evaluate the derivative (should be in (0, 1)).
193+
params (Params): Internal model parameters [:math:`\mu, \nu`] where :math:`\mu`
194+
and :math:`\nu` are the log-shape parameters.
195+
196+
Returns:
197+
float: The partial derivative of the log-PDF with respect to :math:`\nu` at
198+
point :math:`x`, or -infinity if :math:`x` is not in (0, 1).
199+
"""
200+
201+
if not (0 < x < 1):
202+
return -np.inf
203+
204+
a, b = self.params_convert_from_model(params)
205+
return b * (digamma(a + b) - digamma(b) + np.log(1 - x))
206+
207+
def ld_params(self, x: float, params: Params) -> np.ndarray:
208+
r"""Calculates the gradient of the log-PDF with respect to all parameters.
209+
210+
Args:
211+
x (float): The point at which to evaluate the gradient (should be in (0, 1)).
212+
params (Params): Internal model parameters [:math:`\mu, \nu`] where :math:`\mu`
213+
and :math:`\nu` are the log-shape parameters.
214+
215+
Returns:
216+
np.ndarray: An array containing the partial derivatives of the log-PDF with
217+
respect to each parameter [:math:`\mu, \nu`] at point :math:`x`.
218+
"""
219+
220+
return np.array([self.ldmu(x, params), self.ldnu(x, params)])

0 commit comments

Comments
 (0)