Skip to content

Commit 155853f

Browse files
committed
Always sample correctly, but optionally use unscaled pdf for neglogprior
1 parent 1425d9c commit 155853f

File tree

1 file changed

+28
-22
lines changed

1 file changed

+28
-22
lines changed

petab/v1/priors.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,9 @@ class Prior:
6363
If ``True``, the probability density will be rescaled
6464
accordingly and the sample is generated from the truncated
6565
distribution.
66-
If ``False``, the probability density will not account for the
67-
bounds, but any parameter samples outside the bounds will be set to
68-
the value of the closest bound. In this case, the PDF might not match
69-
the sample.
66+
If ``False``, the probability density will not be rescaled
67+
accordingly, but the sample will be generated from the truncated
68+
distribution.
7069
"""
7170

7271
def __init__(
@@ -99,7 +98,7 @@ def __init__(
9998
self._transformation = transformation
10099
self._bounds_truncate = bounds_truncate
101100

102-
truncation = bounds if bounds_truncate else None
101+
truncation = bounds
103102
if truncation is not None:
104103
# for uniform, we don't want to implement truncation and just
105104
# adapt the distribution parameters
@@ -179,27 +178,14 @@ def sample(self, shape=None) -> np.ndarray:
179178
:return: A sample from the distribution.
180179
"""
181180
raw_sample = self.distribution.sample(shape)
182-
return self._clip_to_bounds(self._scale_sample(raw_sample))
181+
return self._scale_sample(raw_sample)
183182

184183
def _scale_sample(self, sample):
185184
"""Scale the sample to the parameter space"""
186185
# we also need to scale parameterScale* distributions, because
187186
# internally, they are handled as (unscaled) log-distributions
188187
return scale(sample, self.transformation)
189188

190-
def _clip_to_bounds(self, x) -> np.ndarray | float:
191-
"""Clip `x` values to bounds.
192-
193-
:param x: The values to clip. Assumed to be on the parameter scale.
194-
"""
195-
if self.bounds is None or self._bounds_truncate:
196-
return x
197-
198-
return np.maximum(
199-
np.minimum(self.ub_scaled, x),
200-
self.lb_scaled,
201-
)
202-
203189
@property
204190
def lb_scaled(self) -> float:
205191
"""The lower bound on the parameter scale."""
@@ -215,8 +201,8 @@ def pdf(self, x) -> np.ndarray | float:
215201
216202
:param x: The value at which to evaluate the PDF.
217203
``x`` is assumed to be on the parameter scale.
218-
:return: The value of the PDF at ``x``. Note that the PDF does
219-
currently not account for the clipping at the bounds.
204+
:return: The value of the PDF at ``x``. ``x`` is assumed to be on the
205+
parameter scale.
220206
"""
221207
x = unscale(x, self.transformation)
222208

@@ -239,7 +225,27 @@ def neglogprior(self, x) -> np.ndarray | float:
239225
``x`` is assumed to be on the parameter scale.
240226
:return: The negative log-prior at ``x``.
241227
"""
242-
return -np.log(self.pdf(x))
228+
# FIXME: the prior is always defined on linear scale
229+
if self._bounds_truncate:
230+
# the truncation is handled by the distribution
231+
return -np.log(self.pdf(x))
232+
233+
# we want to evaluate the prior on the untruncated distribution
234+
x = unscale(x, self.transformation)
235+
236+
# scale the PDF to the parameter scale
237+
if self.transformation == C.LIN:
238+
coeff = 1
239+
elif self.transformation == C.LOG10:
240+
coeff = x * np.log(10)
241+
elif self.transformation == C.LOG:
242+
coeff = x
243+
else:
244+
raise ValueError(f"Unknown transformation: {self.transformation}")
245+
246+
return -np.log(
247+
self.distribution._pdf_transformed_untruncated(x) * coeff
248+
)
243249

244250
@staticmethod
245251
def from_par_dict(

0 commit comments

Comments
 (0)