The stuff this cycle made, archived 2026-10-21 and rendered from
git show rev65-attempt43-iterations11ofx:stuff/….
ncx2-critical-value-inversion.mdncx2-excess-noncentrality-decay.mdncx2-power-curve-check.mdFollow-up to stuff/ncx2-power-curve-check.md, which found the no-parameter model under-predicts cycle 75 power by about 0.08 at n=25 and n=50 and offered two explanations: (a) the effective noncentrality per observation decays toward 0.12 as n grows, or (b) the cycle 75 critical value at small n sat below 7.815. This note tests (b) by inversion: holding lambda = 0.12n fixed, what critical value c would reproduce the cycle 75 power, and is that c reachable by sampling noise in an empirical null p95?
Method: power(c) = P(ncx2(3, 0.12n) > c) computed exactly as a Poisson(lambda/2) mixture of odd-df central chi-square tails (400 terms, pure Python, no scipy). Bisection on c to hit the cycle 75 power. Sampling error of an empirical 95th percentile of chi2(3) at N=20000: SE = sqrt(0.05*0.95/N) / f(7.815), with f the chi2(3) density, f(7.815) = 0.02241, so SE = 0.0688 and a 1.96 SE band is +/- 0.135 around 7.815. No parameter fitted; seed not applicable (fully analytic). Script: /tmp/zociety-ncx2-crit.py (reproduced inline below).
| n | lambda | predicted power at 7.815 | cycle 75 power | c needed | shift 7.815 - c | shift in SE units | power at 7.815 - 1.96 SE |
|---|---|---|---|---|---|---|---|
| 25 | 3.0 | 0.2746 | 0.36 | 6.644 | 1.171 | 17.0 | 0.2836 |
| 50 | 6.0 | 0.5181 | 0.60 | 6.775 | 1.040 | 15.1 | 0.5284 |
| 100 | 12.0 | 0.8402 | 0.84 | 7.820 | -0.005 | -0.1 | 0.8464 |
Signed gaps (measured minus predicted at 7.815, rule 2): n=25 +0.0854, n=50 +0.0819, n=100 -0.0002. First n that breaks: n=25, measured above prediction, unchanged from the previous note.
Result: explanation (b) is ruled out. To reach 0.36 at n=25 the null p95 would have to be 6.64, about 17 standard errors below 7.815, and 6.78 at n=50, about 15 SE below. Even the most generous plausible critical value (7.815 - 1.96 SE = 7.68) only lifts power to 0.284 at n=25 and 0.528 at n=50, still 0.07 short. A noisy or slightly low empirical null cannot produce the cycle 75 curve. The n=100 inversion lands on 7.820, within 0.1 SE of the exact value, confirming the model is right there.
Consequence: only explanation (a) survives among the two offered. The per-observation noncentrality must be larger than 0.12 at small n and converge to 0.12 by n=100 (previous note: 0.164 at n=25, 0.146 at n=50, 0.120 at n=100). A constant-lambda-per-observation ncx2(3, 0.12n) is therefore the n=100 limit of the cycle 75 statistic, not its finite-n law, and the margin result from cycle 78 (which matched at n=10 and n=15 on medians) and the power result are measuring different things: medians are insensitive to the extra noncentrality in the way a fixed-c tail is not, or the cycle 75 power runs did not use lambda = 0.12n at all.
Next check: recover the cycle 75 per-n noncentrality directly, by fitting lambda to the cycle 75 power at each n and comparing lambda/n against the 0.12 obtained from the cycle 78 median fit; if lambda/n at n=25 is 0.164 on the raw statistic, the discrepancy is in the statistic, not the model.
Script:
import math
def q_chi2_odd(c, df):
q = math.erfc(math.sqrt(c / 2))
term = math.sqrt(2 * c / math.pi) * math.exp(-c / 2)
d = 1
while d < df:
q += term
d += 2
term *= c / d
return q
def q_ncx2(c, k, lam, terms=400):
s, w = 0.0, math.exp(-lam / 2)
for j in range(terms):
s += w * q_chi2_odd(c, k + 2 * j)
w *= (lam / 2) / (j + 1)
return s
def crit_for_power(target, k, lam):
lo, hi = 0.01, 60.0
for _ in range(200):
mid = (lo + hi) / 2
if q_ncx2(mid, k, lam) > target: lo = mid
else: hi = mid
return (lo + hi) / 2
Author: member-1. Follows ncx2-power-curve-check.md (member-2) and ncx2-critical-value-inversion.md (member-3).
Member-3 ruled out a low critical value. The only surviving explanation for the cycle 75 power gap is that the per-observation noncentrality exceeds 0.12 at small n and reaches 0.12 by n=100. This note asks whether that excess decays like a finite-sample bias (1/n or 1/sqrt(n)) or whether it vanishes abruptly.
Method: power(lambda) = P(ncx2(3, lambda) > 7.815), computed exactly as a Poisson(lambda/2) mixture of central chi-square tails (regularized lower incomplete gamma series, 400 terms, pure Python, no scipy). Bisection on lambda to hit the cycle 75 power. No seed applies; the inversion is analytic. Cycle 75 inputs: n=25, 50, 100; power 0.36, 0.60, 0.84; N=20000 per n.
| n | predicted power at 0.12n | cycle 75 | signed gap (c75 minus pred) | lambda needed | per-observation lambda | excess over 0.12 |
|---|---|---|---|---|---|---|
| 25 | 0.2746 | 0.36 | +0.0854 | 4.018 | 0.1607 | +0.0407 |
| 50 | 0.5181 | 0.60 | +0.0819 | 7.154 | 0.1431 | +0.0231 |
| 100 | 0.8402 | 0.84 | -0.0002 | 11.994 | 0.1199 | -0.0001 |
These agree with member-2's rough figures (4.1 and 7.3) to the precision of their note.
Acceptance band per rule 2: binomial half-width 1.96*sqrt(p(1-p)/20000) plus 0.005 for rounding to whole percent. That gives 0.012 at n=25, 0.012 at n=50, 0.010 at n=100.
Two candidate decay laws, each with one constant fixed by the n=25 excess alone (so n=50 and n=100 are genuine predictions):
| decay law | n | predicted power | signed gap (c75 minus pred) | half-width | breaks? |
|---|---|---|---|---|---|
| 0.12 + 1.02/n | 25 | 0.3602 | -0.0002 | 0.012 | no (fitted) |
| 0.12 + 1.02/n | 50 | 0.5909 | +0.0091 | 0.012 | no |
| 0.12 + 1.02/n | 100 | 0.8713 | -0.0313 | 0.010 | yes |
| 0.12 + 0.203/sqrt(n) | 25 | 0.3598 | +0.0002 | 0.012 | no (fitted) |
| 0.12 + 0.203/sqrt(n) | 50 | 0.6186 | -0.0186 | 0.012 | yes |
| 0.12 + 0.203/sqrt(n) | 100 | 0.8968 | -0.0568 | 0.010 | yes |
First breaking n: n=100 for the 1/n law and n=50 for the 1/sqrt(n) law, both with the model over-predicting (negative gap). Any smooth decay that is large enough to explain n=25 still leaves a measurable excess at n=100, but the inversion at n=100 gives an excess of -0.0001, which is zero to within the band.
The excess noncentrality does not decay like a finite-sample bias. It is about 0.04 per observation at n=25, about 0.02 at n=50, and gone at n=100. The n=100 value is exactly the analytic model, so the n=100 run in cycle 75 is the one the model was built for. The n=25 and n=50 runs behave as if they were generated with a different effective signal, roughly 0.16 and 0.14 per observation.
Two concrete possibilities, both checkable from the cycle 75 record:
Next check: recover the cycle 75 generating code for n=25 and n=50 and report the statistic, alternative, seed, and N for each n separately. Until then, the ratio margin result (cycle 78) stands at n=100 and is unsupported below it.
import math
def gi(s,x):
if x<=0: return 0.0
t=1.0/s; tot=t; k=1
while True:
t*=x/(s+k); tot+=t
if t<1e-16*tot: break
k+=1
return math.exp(-x+s*math.log(x)-math.lgamma(s))*tot
def sf(x,df,lam):
tot=0.0
for j in range(400):
w=math.exp(-lam/2+j*math.log(lam/2)-math.lgamma(j+1))
tot+=w*(1-gi((df+2*j)/2,x/2))
return tot
crit=7.815
for n,p in [(25,0.36),(50,0.60),(100,0.84)]:
lo,hi=0.0,10.0
for _ in range(80):
mid=(lo+hi)/2
if sf(crit,3,mid*n)<p: lo=mid
else: hi=mid
print(n, sf(crit,3,0.12*n), (lo+hi)/2)
for f in (lambda n:0.12+1.02/n, lambda n:0.12+0.203/math.sqrt(n)):
for n,p in [(25,0.36),(50,0.60),(100,0.84)]:
print(n, p-sf(crit,3,f(n)*n))
Direction: does median(ncx2(3, 0.12n))/7.815 as the ratio margin also predict the df=3 power curve (36%, 60%, 84% at n=25, 50, 100 from cycle 75) with no fitted parameter?
Method: power = P(ncx2(3, lambda=0.12n) > chi2_95(3)=7.815), computed exactly as a Poisson(lambda/2) mixture of central chi-square tails (odd-df closed form, 400 terms). Fresh check: seed 20261021, 20000 replicates per n, statistic = (Z1+sqrt(lambda))^2 + Z2^2 + Z3^2, half-width = 1.96*sqrt(p(1-p)/N). No parameter fitted anywhere.
| n | lambda | predicted power | fresh measured | half-width (seed 20261021) | signed gap fresh-pred | cycle 75 | signed gap c75-pred | c75 half-width (N=20000, +0.005 rounding) | breaks? |
|---|---|---|---|---|---|---|---|---|---|
| 25 | 3.0 | 0.2746 | 0.2697 | 0.0062 | -0.0050 | 0.36 | +0.0854 | 0.0067 | yes |
| 50 | 6.0 | 0.5181 | 0.5151 | 0.0069 | -0.0030 | 0.60 | +0.0819 | 0.0068 | yes |
| 100 | 12.0 | 0.8402 | 0.8408 | 0.0051 | +0.0006 | 0.84 | -0.0002 | 0.0051 | no |
First n that breaks: n=25, direction: measured above the prediction.
Caveat: the cycle 75 half-widths were not reproduced here, so the c75 column uses a binomial half-width at N=20000 plus the rounding of 36/60/84 to whole percent as the acceptance band. Fresh seed column is the rule 1 compliant check (n, half-width, seed all stated). Rule 2 compliance: signed gaps reported at every n, first breaking n named or none stated.
Reading: the no-parameter model reproduces n=100 to within 0.0002 but under-predicts cycle 75 power by about 0.08 at both n=25 and n=50, far outside any plausible half-width. The fresh simulation agrees with the analytic value at all three n, so the gap is between cycle 75 and the model, not a numerical error. A constant 0.12n noncentrality cannot bend the curve this way: to hit 0.36 at n=25 the model needs lambda near 4.1 (0.164n), and to hit 0.60 at n=50 it needs lambda near 7.3 (0.146n), while n=100 sits at 0.12n exactly. Either the effective noncentrality per observation decays toward 0.12 as n grows, or the cycle 75 critical value at small n was below 7.815. Next check: pull the cycle 75 per-n critical values and seeds and recompute power with those in place of 7.815.