codexchernoff-coin-testing-error-decay.py
chernoff-coin-testing-error-decay.py
"""Compute finite-binomial optimal errors and compare with their exponent."""
from math import comb, fsum
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def error_probability(n):
terms = [comb(n, k) * 0.2**k * 0.8**(n-k) for k in range(n//2+1, n+1)]
if n % 2 == 0:
terms.append(0.5 * comb(n, n//2) * 0.2**(n//2) * 0.8**(n//2))
return fsum(terms)
def main():
n = np.arange(1, 161)
errors = np.array([error_probability(int(k)) for k in n])
c = -np.log(0.8)
bound = 0.5 * np.exp(-c*n)
fig, (left, right) = plt.subplots(1, 2, figsize=(9.4, 4.6), layout="constrained")
fig.set_facecolor("white")
left.semilogy(n, errors, color="#186c99", linewidth=2, label="Exact optimal average error")
left.semilogy(n, bound, color="#af5423", linestyle="--", linewidth=1.7,
label="Chernoff bound: ½ exp(−nC)")
left.set(xlabel="Number n of independent observations", ylabel="Error probability",
title="Equal-prior coin testing")
left.legend(frameon=False, fontsize=9.5)
right.plot(n, -np.log(errors)/n, color="#186c99", linewidth=2,
label="−ln(exact error) / n")
right.axhline(c, color="#af5423", linestyle="--", linewidth=1.7,
label="Limiting C ≈ 0.22314")
right.set(xlabel="Number n of independent observations", ylabel="Nats per observation",
title="Finite exponent approaches the limit", ylim=(0.18, 0.9))
right.legend(frameon=False, fontsize=10)
right.text(0.97, 0.96, "n = 1: 1.60944 (above plot)", transform=right.transAxes,
ha="right", va="top", fontsize=9.5)
for ax in (left, right):
ax.set_xlim(1, 160)
ax.spines[["top", "right"]].set_visible(False)
ax.grid(alpha=0.18)
fig.suptitle("Stipulated Bernoulli laws: success probabilities 0.2 and 0.8", fontsize=14)
assert np.all(errors <= bound * (1 + 1e-12))
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=130, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()