1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Monte-Carlo accuracy diagnostics for a single chain.
//!
//! A posterior summary reported without one of these is a point estimate of unknown
//! precision. The two that matter for a scalar summary of one chain:
//!
//! - [`ess`] — the **effective sample size**: how many *independent* draws the chain's
//! `n` correlated draws are worth. An elliptical-slice chain that rejects and re-emits
//! its current state (the bracket-exhaustion path) produces duplicate draws, so `n` is
//! an upper bound that can be far from the truth.
//! - [`mcse_proportion`] — the standard error of a **tail probability** estimated from
//! that chain (an `lfsr` is exactly such a probability). This is what says whether a
//! site sitting near a reporting threshold is genuinely near it, or just under-sampled.
//!
//! Note the name collision this crate lives with: throughout `engine::ess`, "ESS" means
//! *elliptical slice sampling*. Here — and only here — it means *effective sample size*.
/// Effective sample size of a scalar chain, by Geyer's initial monotone positive sequence.
///
/// `ess = n / (1 + 2 Σ ρ_t)`, with the autocorrelation sum truncated where Geyer's adjacent
/// pair sums `Γ_k = ρ_{2k+1} + ρ_{2k+2}` first go non-positive, and each pair sum capped by
/// its predecessor so the sequence stays monotone. Truncating at the first non-positive pair
/// sum is what makes this cheap: the sum runs over the few dozen lags that actually carry
/// autocorrelation rather than all `n`, so the cost is `O(n · lag*)`, not `O(n²)`.
///
/// Returns `n` for a chain too short to estimate from (`n < 4`) or a constant one — a chain
/// that never moves has no autocorrelation to discount, and callers still need a finite
/// divisor. That is the right answer for [`mcse_proportion`]'s purposes (its smoothing keeps
/// a constant indicator chain from reporting zero error), but it does mean `ess == n` is not
/// on its own evidence of good mixing.
///
/// Accumulates in `f64`: the lag products are sums of `n` like-signed terms, and the whole
/// estimate is a ratio of small differences. The chain is centered **once** into an `f64`
/// buffer, so each lag is a plain dot product over that buffer rather than an `n`-long
/// re-centering — this runs per `(site, group)` and there are thousands of those.
/// **Split-R̂** for one chain: the Gelman–Rubin statistic computed by cutting the chain
/// into consecutive segments and treating them as separate chains.
///
/// Why split rather than multi-chain. R̂ compares between-segment variance against
/// within-segment variance, so it is sensitive to exactly the failure a single long run
/// hides: a chain still drifting, where the first half and the second half are sampling
/// different regions. `1.0` means the segments are indistinguishable; the conventional
/// threshold is `1.01`, and anything above `1.1` should be read as "this has not
/// converged" rather than as a mild warning.
///
/// What it CANNOT see, and this bound matters: a mode both halves are stuck in. Split-R̂
/// certifies stationarity, not that the chain found the right place — for that there is
/// no substitute for independent chains from dispersed starts.
///
/// Returns `1.0` for a chain too short to split (nothing to compare), and `1.0` for a
/// perfectly constant chain, since a zero within-segment variance means the segments
/// agree exactly rather than that they disagree infinitely.
/// Monte-Carlo standard error of a probability `p` estimated from a chain with effective
/// sample size `ess` — e.g. the `lfsr`, which is a posterior tail probability.
///
/// Uses the Jeffreys (+½) smoothed proportion rather than the plug-in `√(p(1−p)/ess)`.
/// The plug-in reports an error of **exactly zero** when no draw fell on the minority side
/// (`p = 0`) — i.e. it claims infinite precision precisely at the most significant sites,
/// which are the ones whose ranking a reader is most likely to trust. Smoothing reports the
/// resolution the chain can actually support there (about `0.7 / ess`), which is the honest
/// statement: *not observed in `ess` effective draws*, not *impossible*.