Skip to content

Commit

Permalink
fix cdf/pdf outside bounds / <0
Browse files Browse the repository at this point in the history
  • Loading branch information
dweindl committed Dec 11, 2024
1 parent 057457f commit 1425d9c
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 18 deletions.
8 changes: 4 additions & 4 deletions doc/example/distributions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@
},
{
"metadata": {
"collapsed": true,
"jupyter": {
"is_executing": true
}
"collapsed": true
},
"cell_type": "code",
"source": [
Expand All @@ -50,6 +47,9 @@
" # pdf\n",
" xmin = min(sample.min(), prior.lb_scaled if prior.bounds is not None else sample.min())\n",
" xmax = max(sample.max(), prior.ub_scaled if prior.bounds is not None else sample.max())\n",
" padding = 0.1 * (xmax - xmin)\n",
" xmin -= padding\n",
" xmax += padding\n",
" x = np.linspace(xmin, xmax, 500)\n",
" y = prior.pdf(x)\n",
" ax.plot(x, y, color='red', label='pdf')\n",
Expand Down
56 changes: 42 additions & 14 deletions petab/v1/distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,26 +138,48 @@ def pdf(self, x) -> np.ndarray | float:
:param x: The value at which to evaluate the PDF.
:return: The value of the PDF at ``x``.
"""
# handle the log transformation; see also:
# https://en.wikipedia.org/wiki/Probability_density_function#Scalar_to_scalar
chain_rule_factor = (
(1 / (x * np.log(self._logbase))) if self._logbase else 1
)
return (
self._pdf(self._log(x))
* chain_rule_factor
* self._truncation_normalizer
if self._trunc is None:
return self._pdf_transformed_untruncated(x)

return np.where(
(x >= self.trunc_low) & (x <= self.trunc_high),
self._pdf_transformed_untruncated(x) * self._truncation_normalizer,
0,
)

@abc.abstractmethod
def _pdf(self, x) -> np.ndarray | float:
def _pdf_untransformed_untruncated(self, x) -> np.ndarray | float:
"""Probability density function of the underlying distribution at x.
:param x: The value at which to evaluate the PDF.
:return: The value of the PDF at ``x``.
"""
...

def _pdf_transformed_untruncated(self, x) -> np.ndarray | float:
"""Probability density function of the transformed, but untruncated
distribution at x.
:param x: The value at which to evaluate the PDF.
:return: The value of the PDF at ``x``.
"""
if self.logbase is False:
return self._pdf_untransformed_untruncated(x)

# handle the log transformation; see also:
# https://en.wikipedia.org/wiki/Probability_density_function#Scalar_to_scalar
chain_rule_factor = (
(1 / (x * np.log(self._logbase))) if self._logbase else 1
)

with np.errstate(invalid="ignore"):
return np.where(
x > 0,
self._pdf_untransformed_untruncated(self._log(x))
* chain_rule_factor,
0,
)

@property
def logbase(self) -> bool | float:
"""The base of the log transformation.
Expand Down Expand Up @@ -185,7 +207,13 @@ def _cdf_transformed_untruncated(self, x) -> np.ndarray | float:
:param x: The value at which to evaluate the CDF.
:return: The value of the CDF at ``x``.
"""
return self._cdf_untransformed_untruncated(self._log(x))
if not self.logbase:
return self._cdf_untransformed_untruncated(x)

with np.errstate(invalid="ignore"):
return np.where(
x < 0, 0, self._cdf_untransformed_untruncated(self._log(x))
)

def _cdf_untransformed_untruncated(self, x) -> np.ndarray | float:
"""Cumulative distribution function of the underlying
Expand Down Expand Up @@ -263,7 +291,7 @@ def __repr__(self):
def _sample(self, shape=None) -> np.ndarray | float:
return np.random.normal(loc=self._loc, scale=self._scale, size=shape)

def _pdf(self, x) -> np.ndarray | float:
def _pdf_untransformed_untruncated(self, x) -> np.ndarray | float:
return norm.pdf(x, loc=self._loc, scale=self._scale)

def _cdf_untransformed_untruncated(self, x) -> np.ndarray | float:
Expand Down Expand Up @@ -314,7 +342,7 @@ def __repr__(self):
def _sample(self, shape=None) -> np.ndarray | float:
return np.random.uniform(low=self._low, high=self._high, size=shape)

def _pdf(self, x) -> np.ndarray | float:
def _pdf_untransformed_untruncated(self, x) -> np.ndarray | float:
return uniform.pdf(x, loc=self._low, scale=self._high - self._low)

def _cdf_untransformed_untruncated(self, x) -> np.ndarray | float:
Expand Down Expand Up @@ -360,7 +388,7 @@ def __repr__(self):
def _sample(self, shape=None) -> np.ndarray | float:
return np.random.laplace(loc=self._loc, scale=self._scale, size=shape)

def _pdf(self, x) -> np.ndarray | float:
def _pdf_untransformed_untruncated(self, x) -> np.ndarray | float:
return laplace.pdf(x, loc=self._loc, scale=self._scale)

def _cdf_untransformed_untruncated(self, x) -> np.ndarray | float:
Expand Down

0 comments on commit 1425d9c

Please sign in to comment.