@@ -30,11 +30,20 @@ def log_partition(self, xp=np) -> float:
3030 """
3131 Compute the log-partition function (normalizer) of the truncated Gaussian.
3232
33- This is the log of the normalization constant Z of the truncated normal:
33+ For the exponential-family interface this is the full cumulant, made of
34+ two parts:
3435
35- Z = Φ((b - μ)/σ) - Φ((a - μ)/σ)
36+ 1. the untruncated Gaussian log-partition ``A(η) = μ²/(2σ²) + log σ``
37+ (identical to ``NormalMessage.log_partition``), and
38+ 2. the truncation-mass correction ``log Z`` with
39+ ``Z = Φ((b - μ)/σ) - Φ((a - μ)/σ)``, where Φ is the standard normal
40+ CDF and ``[a, b]`` are the truncation bounds.
3641
37- where Φ is the standard normal CDF and [a, b] are the truncation bounds.
42+ Previously only ``log Z`` was returned, dropping the Gaussian term. That
43+ made the generic exponential-family pdf integrate to
44+ ``σ·exp(μ²/2σ²)`` (e.g. 2.27 for the unit case) instead of 1.0 — the path
45+ the EP machinery consumes. Sampling and ``log_prior_from_value`` use a
46+ separate, correct path and were unaffected.
3847
3948 Returns
4049 -------
@@ -43,10 +52,14 @@ def log_partition(self, xp=np) -> float:
4352 """
4453 from scipy .stats import norm
4554
55+ # Untruncated Gaussian log-partition — see NormalMessage.log_partition.
56+ gaussian = (self .mean ** 2 ) / (2 * self .sigma ** 2 ) + xp .log (self .sigma )
57+
4658 a = (self .lower_limit - self .mean ) / self .sigma
4759 b = (self .upper_limit - self .mean ) / self .sigma
4860 Z = norm .cdf (b ) - norm .cdf (a )
49- return xp .log (Z ) if Z > 0 else - xp .inf
61+ log_Z = xp .log (Z ) if Z > 0 else - xp .inf
62+ return gaussian + log_Z
5063
5164 log_base_measure = - 0.5 * np .log (2 * np .pi )
5265
@@ -472,9 +485,19 @@ def log_prior_from_value(self, value: float, xp=np) -> float:
472485 """
473486 Compute the log prior probability of a given physical value under this truncated Gaussian prior.
474487
475- This accounts for truncation by normalizing the Gaussian density over the
476- interval [lower_limit, upper_limit], returning -inf if the value lies outside
477- these limits.
488+ Returns ``log p(value)`` in density form, up to an additive constant, and
489+ ``-inf`` for values outside ``[lower_limit, upper_limit]``.
490+
491+ The value-independent constants ``-log(sigma) - 0.5*log(2*pi)`` (the
492+ Gaussian normaliser) and ``-log(Z)`` (the truncation mass) are dropped, so
493+ this matches the constant-dropping convention already used by
494+ ``NormalMessage.log_prior_from_value`` (and Uniform / LogUniform / LogGaussian).
495+ Previously this method returned the *fully normalised* truncated density,
496+ making it the odd one out — harmless to posterior shape (constants cancel
497+ in the Metropolis ratio and nested samplers use the unit-cube transform),
498+ but inconsistent for anyone reading absolute ``log_prior`` values or doing
499+ evidence arithmetic. The dropped constant is recoverable via
500+ ``TruncatedGaussianPrior.log_normalisation``.
478501
479502 Parameters
480503 ----------
@@ -483,33 +506,18 @@ def log_prior_from_value(self, value: float, xp=np) -> float:
483506
484507 Returns
485508 -------
486- The log prior probability of the given value, or -inf if outside truncation bounds.
509+ The log prior density at the given value up to an additive constant, or
510+ -inf if outside the truncation bounds.
487511 """
488512
489- if xp .__name__ .startswith ("jax" ):
490- import jax .scipy .stats as jstats
491- norm = jstats .norm
492- else :
493- from scipy .stats import norm
494-
495- # Normalization term (truncation)
496- a = (self .lower_limit - self .mean ) / self .sigma
497- b = (self .upper_limit - self .mean ) / self .sigma
498- Z = norm .cdf (b ) - norm .cdf (a )
499-
500- # Log pdf
513+ # Density-form quadratic, constants dropped (see docstring / NormalMessage).
501514 z = (value - self .mean ) / self .sigma
502- log_pdf = (
503- - 0.5 * z ** 2
504- - xp .log (self .sigma )
505- - 0.5 * xp .log (2.0 * xp .pi )
506- )
507- log_trunc_pdf = log_pdf - xp .log (Z )
515+ log_pdf = - 0.5 * z ** 2
508516
509- # Truncation mask (must be xp.where for JAX)
517+ # Truncation mask (must be xp.where for JAX).
510518 in_bounds = (self .lower_limit <= value ) & (value <= self .upper_limit )
511519
512- return xp .where (in_bounds , log_trunc_pdf , - xp .inf )
520+ return xp .where (in_bounds , log_pdf , - xp .inf )
513521
514522 def __str__ (self ):
515523 """
0 commit comments