Skip to content

Commit 1425d9c

Browse files
committed
fix cdf/pdf outside bounds / <0
1 parent 057457f commit 1425d9c

File tree

2 files changed

+46
-18
lines changed

2 files changed

+46
-18
lines changed

doc/example/distributions.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
},
2424
{
2525
"metadata": {
26-
"collapsed": true,
27-
"jupyter": {
28-
"is_executing": true
29-
}
26+
"collapsed": true
3027
},
3128
"cell_type": "code",
3229
"source": [
@@ -50,6 +47,9 @@
5047
" # pdf\n",
5148
" xmin = min(sample.min(), prior.lb_scaled if prior.bounds is not None else sample.min())\n",
5249
" xmax = max(sample.max(), prior.ub_scaled if prior.bounds is not None else sample.max())\n",
50+
" padding = 0.1 * (xmax - xmin)\n",
51+
" xmin -= padding\n",
52+
" xmax += padding\n",
5353
" x = np.linspace(xmin, xmax, 500)\n",
5454
" y = prior.pdf(x)\n",
5555
" ax.plot(x, y, color='red', label='pdf')\n",

petab/v1/distributions.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -138,26 +138,48 @@ def pdf(self, x) -> np.ndarray | float:
138138
:param x: The value at which to evaluate the PDF.
139139
:return: The value of the PDF at ``x``.
140140
"""
141-
# handle the log transformation; see also:
142-
# https://en.wikipedia.org/wiki/Probability_density_function#Scalar_to_scalar
143-
chain_rule_factor = (
144-
(1 / (x * np.log(self._logbase))) if self._logbase else 1
145-
)
146-
return (
147-
self._pdf(self._log(x))
148-
* chain_rule_factor
149-
* self._truncation_normalizer
141+
if self._trunc is None:
142+
return self._pdf_transformed_untruncated(x)
143+
144+
return np.where(
145+
(x >= self.trunc_low) & (x <= self.trunc_high),
146+
self._pdf_transformed_untruncated(x) * self._truncation_normalizer,
147+
0,
150148
)
151149

152150
@abc.abstractmethod
153-
def _pdf(self, x) -> np.ndarray | float:
151+
def _pdf_untransformed_untruncated(self, x) -> np.ndarray | float:
154152
"""Probability density function of the underlying distribution at x.
155153
156154
:param x: The value at which to evaluate the PDF.
157155
:return: The value of the PDF at ``x``.
158156
"""
159157
...
160158

159+
def _pdf_transformed_untruncated(self, x) -> np.ndarray | float:
160+
"""Probability density function of the transformed, but untruncated
161+
distribution at x.
162+
163+
:param x: The value at which to evaluate the PDF.
164+
:return: The value of the PDF at ``x``.
165+
"""
166+
if self.logbase is False:
167+
return self._pdf_untransformed_untruncated(x)
168+
169+
# handle the log transformation; see also:
170+
# https://en.wikipedia.org/wiki/Probability_density_function#Scalar_to_scalar
171+
chain_rule_factor = (
172+
(1 / (x * np.log(self._logbase))) if self._logbase else 1
173+
)
174+
175+
with np.errstate(invalid="ignore"):
176+
return np.where(
177+
x > 0,
178+
self._pdf_untransformed_untruncated(self._log(x))
179+
* chain_rule_factor,
180+
0,
181+
)
182+
161183
@property
162184
def logbase(self) -> bool | float:
163185
"""The base of the log transformation.
@@ -185,7 +207,13 @@ def _cdf_transformed_untruncated(self, x) -> np.ndarray | float:
185207
:param x: The value at which to evaluate the CDF.
186208
:return: The value of the CDF at ``x``.
187209
"""
188-
return self._cdf_untransformed_untruncated(self._log(x))
210+
if not self.logbase:
211+
return self._cdf_untransformed_untruncated(x)
212+
213+
with np.errstate(invalid="ignore"):
214+
return np.where(
215+
x < 0, 0, self._cdf_untransformed_untruncated(self._log(x))
216+
)
189217

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

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

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

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

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

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

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

0 commit comments

Comments
 (0)