# 教材位置：第2章，第2.7节；图 2-1。
# 单位、变量与形状：rolls、means、n 均为 (20000,)；均值和标准误按骰子点数计。
# 学习目标：观察大数定律下的累计样本均值，并区分标准误与一次实验的实际误差。
# 核心关系：独立同分布且期望有限的样本满足样本均值趋向总体均值；公平骰子的
# E[X]=3.5、Var(X)=35/12，所以 n 次独立抽样均值的标准误为 sqrt((35/12)/n)。
# 数据与输出：模拟 20000 次骰子点数，打印若干累计均值和末尾的理论标准误，
# 并保存累计均值图 e03_lln.png；图展示的是一次实验的路径。
# 运行方式：安装 NumPy、Matplotlib 后执行 python e03_lln.py；图片写入当前工作目录。

import numpy as np
# pyplot 用于创建和保存图形；本例直接写文件，不依赖弹出绘图窗口。
import matplotlib.pyplot as plt
# 在创建第一幅图之前配置可跨系统选择的中文字体。
from plot_style import configure_chinese_plot

configure_chinese_plot()

# 固定种子，让同一运行环境下的整条累计均值曲线可以复现。
rng = np.random.default_rng(7)
# n=[1,...,20000]，形状为 (20000,)；n[i] 对应使用前 i+1 个观测的均值。
n = np.arange(1, 20001)
# 上界 7 不包含在内，生成与 n 等长的公平骰子样本。
rolls = rng.integers(1, 7, size=n.size)
# cumsum 保存前缀和；逐元素除以 n，同一次实验的前 1、2、...、20000 次样本被逐步复用。
# 因此这些累计均值彼此有关联，并不是每个 n 都重新做一轮独立实验。
means = np.cumsum(rolls) / n
# NumPy 从 0 开始编号，所以第 10 个累计均值的位置为 9，其他下标同理。
print("n=10、100、1000、20000 时的累计均值：", means[[9, 99, 999, 19999]])
# n[-1] 是最后一个样本量；这里计算的是均值在重复抽样下的标准差。
# 标准误不是本次 |样本均值-3.5|，也不是每条路径必然遵守的误差上界。
print("n=20000 时的理论标准误：", np.sqrt((35 / 12) / n[-1]))
# 枚举所有等概率点数，检查理论均值；该断言不要求随机样本均值恰好等于 3.5。
assert np.isclose(np.mean(np.arange(1, 7)), 3.5)
# 创建一幅 6×3 英寸的图；fig 管理整幅图，ax 管理坐标轴和曲线。
fig, ax = plt.subplots(figsize=(6.3, 3))
# 横轴使用对数刻度，让早期波动和后期稳定趋势都容易观察；样本量本身没有被改写。
ax.semilogx(n, means, label="累计样本均值")
# 用水平虚线标出理论均值，方便比较本次样本路径与极限目标。
ax.axhline(3.5, color="C1", linestyle="--", label="E[X]=3.5")
ax.set(xlabel="样本量 n（对数刻度）", ylabel="均值")
# 大数定律不保证误差随 n 单调下降；曲线短暂偏离目标并不与定律矛盾。
# 图例区分样本路径和理论值，tight_layout 调整边距以减少标签被裁切的可能。
ax.legend()
fig.tight_layout()
# dpi 控制保存图片的像素密度；关闭图形释放资源，不会删除已经保存的文件。
fig.savefig("e03_lln.png", dpi=160)
plt.close(fig)
