= Solution
Write $\Phi$ for the <standard normal distribution function>. Integrating out the twenty independent past durations gives the <threshold-censored normal likelihood>
$$
L(\mu,\sigma)=\prod_{c\in C_L}\Phi\left(\frac{\mu-c}{\sigma}\right)^{n_L(c)}\prod_{c\in C_E}\Phi\left(\frac{c-\mu}{\sigma}\right)^{n_E(c)}.
$$
The frequencies $n_L,n_E$ are those in part (a). For a future day, the conditional late-arrival <probability> is $\pi(\mu,\sigma)=\Phi((\mu-30)/\sigma)$. <Conditional independence> of the five future durations gives $h(\mu,\sigma)=\pi(\mu,\sigma)^5$.
Exactly three primitive random coordinates suffice. Draw $\mu,\sigma$ from the same priors and one independent $U\sim\operatorname{Unif}(0,1)$. Reject if $U\geq L(\mu,\sigma)$; otherwise return whether $U<L(\mu,\sigma)h(\mu,\sigma)$. Conditional on acceptance and the parameters, $U$ is uniform on an interval of length $L$; the true-answer subinterval has length $Lh$. Reusing this same uniform value is therefore intentional, not an independence mistake.
Indeed the two output contributions at each parameter value are
$$
\mathbb P(A,Y=1\mid\mu,\sigma)=Lh,\qquad \mathbb P(A,Y=0\mid\mu,\sigma)=L(1-h).
$$
These are exactly the contributions obtained by integrating the original twenty-five duration noises. Multiplication by the common <prior density> and <normalization> therefore give the same joint <posterior distribution> of $(\mu,\sigma,Y)$. The revised program preserves the query and parameter posterior, but does not retain individual latent durations. This is <uniform-interval encoding of conditioning and a binary query>.
Here is an executable version, reusing the definitions from part (a). <Logarithms> avoid multiplying many small <probabilities>.
``
def normal_logcdf(z):
value = 0.5 * math.erfc(-z / math.sqrt(2.0))
return math.log(value) if value > 0.0 else -math.inf
def journey_loglik(mu, sigma):
return (sum(n * normal_logcdf((mu - c) / sigma)
for c, n in LATE.items())
+ sum(n * normal_logcdf((c - mu) / sigma)
for c, n in EARLY.items()))
def collapsed_trace(rng):
mu = rng.uniform(0.0, 60.0)
sigma = rng.uniform(1.0, 20.0)
log_likelihood = journey_loglik(mu, sigma)
# This uniform lies in (0, 1], avoiding log(0).
log_u = math.log(1.0 - rng.random())
if log_u >= log_likelihood:
return None
log_h = 5.0 * normal_logcdf((mu - 30.0) / sigma)
return log_u < log_likelihood + log_h
``
`accepted_average(collapsed_trace, n, rng)` consequently has the same target as the original program. The ideal three-dimensional <sample space> is $(0,60)\times(1,20)\times(0,1)$ with product uniform <probability measure>, conditioned on the displayed acceptance event. Finite-precision normal tails can underflow to zero in this simple implementation; the mathematical equivalence concerns exact <probabilities>, and the numerical check in part (d) independently integrates the <likelihood> in the region carrying appreciable posterior mass.
Back to article page