feat(solve): monte_carlo_integral 落地,修掉体积符号真 bug

实现由用户本人写;Codex 只碰工程侧 + 一处纯 bug(决策 31)。

- 真 bug:体积写成 lows - highs,奇数维为负(1D x^2 于 [0,1] 得 -0.3324、3D 得 -0.5005);
  2D 因两个负号相乘"看着对"(+0.2499),把 bug 藏住。改成 widths = highs - lows 后
  复测 0.3324 / 0.5005 / 1.9971([0,2])全部正确
- docstring:补 func / n / random_state 与 Raises;写清 metrics["mean"] 才是积分估计值、
  data 是 n 个样本各自的贡献(决策 51);修掉跑不通的 Examples(data 是数组,bool(数组) 会抛)
- tests/test_solve.py:摘 1 条 skip + 新增 4 条(3D 奇数维 / [0,2] 宽度 / 2D+params / 非法 bounds)
- 验证:197 收集 / 166 passed / 31 skipped / 0 xfail,ruff 0,pyright 0
- docs:STATUS 决策 51 + 3 条待办 + 下一站 random_walk;CHANGELOG;易错点 22/23,第 3 条复发 +1
This commit is contained in:
guzhujushi committed 2026-10-05 15:55:12 +08:00
1 parent b0c4b4a413
commit ed1f9f0c53
5 files changed
+207 -37

No files matched your search

+64 -27
View File
@@ -1,6 +1,6 @@
# STATUS — 当前进度
> 更新日期:2026-10-03
> 更新日期:2026-10-05
## 本次任务
@@ -21,8 +21,9 @@
当前 **L0 11/12**:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、
`detect_outliers`、`normalize`/`standardize`、`describe`、`correlation_matrix`、`bootstrap_ci`、
`monte_carlo`(**用户写**,2026-10-04);另**提前落地**了 L1 的 `confidence_interval` / `t_test` /
`monte_carlo_probability`(**用户写**,2026-10-05)。剩余 1 项:`save_figure`(Codex 写)。
`monte_carlo` 的口径已拍板(决策 48),`ci_low`/`ci_high` 的语义与 `monte_carlo_probability` 的 6 条待办见"下一步"。
`monte_carlo_probability`、`monte_carlo_integral`(**用户写**,2026-10-05)。剩余 1 项:`save_figure`(Codex 写)。
`monte_carlo` 的口径已拍板(决策 48);`ci_low`/`ci_high` 的语义、`monte_carlo_probability` 剩下的
1 条待办、`monte_carlo_integral` 的 3 条待办,都见"下一步"。
## 任务清单
@@ -274,13 +275,30 @@
④ 文案:NaN 提示里的 "simulator" 改成 "event"(从 `monte_carlo` 抄来没改)
- **测试:摘掉 2 条 skip**(`test_monte_carlo_probability_of_standard_normal`、`test_monte_carlo_is_reproducible`
—— 实现写完还挂着 skip,正是易错点 9)并新增 5 条,`solve` 段共 24 条
- [x] `solve/simulate.py` 的 `monte_carlo_integral`(**L1,S 档,算法由用户本人写**,2026-10-05)
- 核心:`bounds` → 自己造 `(n, d)` 均匀样本 → 逐点求值 → `mean(func(X) × 体积)`;
自造 `sampler` / `simulator` 再复用 `monte_carlo` —— "造批 → 一次算 → 汇总"这套骨架的第二次复用
- **Codex 只改了一处真 bug(决策 31,纯 bug)**:体积写成 `wights = lows - highs`(名字打错 + 减反)。
奇数维体积为负:1D `x²` 于 `[0,1]` 实测 **-0.3324**(应 +1/3)、3D `x` 得 **-0.5005**(应 +1/2);
而 **2D `x·y` 得 +0.2499 "看着对"** —— 两个负号相乘抵消,把 bug 藏住了。
改成 `widths = highs - lows` 后复测 **0.3324 / 0.5005 / 1.9971(`[0,2]`)** 全部正确
- **Codex 只碰工程侧**:① docstring 补 `func` / `n` / `random_state` 三节 `Parameters` 与 `Raises`;
② `Returns` 写清"**`metrics["mean"]` 才是积分估计值**,`data` 是 `n` 个样本各自的贡献";
③ **修掉跑不通的 Examples**(原版断言 `abs(r.data - 1 / 3) < 0.01` —— `data` 是数组,
`bool(数组)` 会抛 `ValueError: truth value ... is ambiguous`),改成断言 `metrics["mean"]`;
④ 格式:`bounds_arr[:,0]` → `[:, 0]`、函数间空行(`def random_walk` 前只留了 1 个空行)
- **测试:新增 4 条 + 摘掉 1 条 skip**(`solve` 段共 30 条)—— 1D 收敛到 1/3(n=2e5)、
**3D 抓体积符号**(奇数维)、**`[0,2]` 抓宽度**(偶数维藏不住)、2D + `name`/`dimension`/`bounds` 三个 params、
`(1, 0)` 抛 `ValueError`。三条"抓符号"用例就是照着上面那个"2D 掩盖效应"设计的
- ⏳ **留给用户 3 条**(见"下一步"):`result.message` 无条件覆盖(失败被写成"成功")、
`bounds` 形状没校验(`[0, 1]` 抛裸 `IndexError`)、`n: int` 与全模块 `int | np.integer` 不一致
- [ ] 剩余 1 项:`save_figure`(A 档 = 工程,**Codex 直接写**)
### 验证结果
| 检查 | venv | conda `mcm-toolkit` |
|---|---|---|
| `pytest` | 见下(单列) | **161 passed, 32 skipped(0 xfail)** |
| `pytest` | 见下(单列) | **166 passed, 31 skipped(0 xfail)** |
| `import mcm_toolkit` | 0.0.1 / 16 项 | 0.0.1 / 16 项 |
| `ruff check` | All checks passed | **All checks passed**(0 error) |
| `scripts/doctor.ps1` | 跑通 | — |
@@ -290,15 +308,14 @@
(修配置之前是 17 errors,全是"无法解析导入 numpy / pandas",见决策 29 与
`docs/04-environment.md` 坑 7)
- base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致)
- **193 条用例:161 passed / 32 skipped / 0 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
`test_core.py` 23、`test_solve.py` 25(+16,含摘掉 2 条 skip)、`test_viz.py` 8、`test_decision.py` 7、`test_stats.py` 53、
`test_models.py` 5
- **197 条用例:166 passed / 31 skipped / 0 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
`test_core.py` 23、`test_solve.py` 30(上一轮 25,本轮摘 1 条 skip + 新增 4 条)、`test_viz.py` 8、`test_decision.py` 7、
`test_stats.py` 53、`test_models.py` 5
- **`xfail` 再次清零**:`correlation_matrix` 的契约冲突已按方案 A 解决(决策 41),那条用例转正
- 上一轮那 7 条 xfail 已全部转正,决策 30 那套信号(XPASS → 摘 mark)跑完过一个完整闭环
- `examples/01_clean_and_describe.py` 实测跑通(exit 0);`00_quickstart.py` 仍等 `stats` / `viz`
- 32 条 skip 是等实现的算法(`train_test_split`、`monte_carlo_integral`、`anova`、`fit_distribution` 等);
`monte_carlo_probability` 已落地,摘掉 2 条 skip(本轮净 +3 条用例);
`bootstrap_ci` 已落地摘掉 3 条 skip(本轮新增 6 条用例,净 +3);`test_solve.py` 9 条按决策 26 改为逐条 skip;
- 31 条 skip 是等实现的算法(`train_test_split`、`random_walk`、`markov_*`、`anova`、`fit_distribution` 等);
`monte_carlo_probability` / `monte_carlo_integral` 已落地,分别摘掉 2 条与 1 条 skip;
`TestValidate` 的 4 条仍挂着 —— `check_*` 还是 `NotImplementedError`,但它们实现时应当直接复用 `as_array`
- **本轮回归(已修)**:`transform.py` 上一版有 3 处真 bug —— 修前 **7 failed / 75 passed**,
且 pyright 在同一处报 `reportArgumentType`。**不是静态误报**:pyright CLI 复现 + 测试真红。
@@ -313,9 +330,10 @@
- [x] ~~Gitea 远程尚未配置~~ → **已配置并推送完成**,见上面 D 节与决策 19/20。
note:仓库当前是 **Public**(匿名 `git ls-remote` 可读),要改私有去 Gitea 仓库设置。
- [ ] L0 完成 **11/12**(`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、
`detect_outliers`、`normalize`、`standardize`、`describe`、`correlation_matrix`、`bootstrap_ci`),
另 L1 的 `confidence_interval` / `t_test` 已提前落地;
**只剩 `monte_carlo`(用户写)与 `save_figure`(Codex 写)**,详情见 E 节与"下一步"
`detect_outliers`、`normalize`、`standardize`、`describe`、`correlation_matrix`、`bootstrap_ci`、
`monte_carlo`(用户写,2026-10-04));另 L1 的 `confidence_interval` / `t_test` /
`monte_carlo_probability` / `monte_carlo_integral` 已提前落地;
**L0 只剩 `save_figure`(Codex 写)**,详情见 E 节与"下一步"
- [ ] 本机 `exec_command` 在沙箱模式下**完全不可用**
(`helper_unknown_error: setup refresh had errors`),只有提权
(`require_escalated`)模式可用。沙箱模式只允许写 `D:\MCM&ICM_Toolkit`。
@@ -415,26 +433,33 @@ docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向
用户 2026-10-05 按"6 条待办"自行改定。Wilson 在 p 贴近 0 / 1 时不会退化成零宽:实测 p=1、n=1000 从 `(1.0, 1.0)`
变成 `(0.99617, 1.0)`;p=0 从"判失败"变成 `(0.0, 0.00383)`(原 Wald 是 0)。区间口径与
`statsmodels.proportion_confint(method="wilson")` 对齐,已加成对照测试。
**遗留两条**:`event: Callable[..., bool]` 注解与"吃整批、返回数组"不符;警告文案把 p≈1 也说成"小概率事件"。
另:`z_score^2` 那处是 Codex 修的真 bug(`^` 是异或,见易错点 21)——按决策 31 属纯 bug,未改任何语义。
**遗留**(2026-10-05 第二轮复核):`event: Callable[..., bool]` 注解与"吃整批、返回数组"不符(**待改**);
警告文案把 p≈1 也说成"小概率事件" —— **用户已于同日改掉**(现在两半都覆盖,见 CHANGELOG 本轮)。
| 51 | `monte_carlo_integral` 的 `data` = **n 个样本各自的贡献**(`func(X_i) × 体积`);积分估计值看 `metrics["mean"]` |
用户 2026-10-05 写完实现。Codex 审查只改了一处**纯 bug**(决策 31):体积 `lows - highs` → `highs - lows` ——
奇数维体积为负(1D `x²` 于 `[0,1]` 实测 **-0.3324**、3D `x` 得 **-0.5005**),而 **2D 因两个负号相乘"看着对"**
(+0.2499),把 bug 藏住了;改后实测 0.3324 / 0.5005 / 1.9971 全部正确。docstring 与测试都按上述口径写死。
**留给用户 3 条**:① `result.message` 无条件覆盖(失败被写成"成功");② `bounds` 形状没校验(`[0, 1]` 抛裸 `IndexError`);
③ `n: int` 与全模块的 `int | np.integer` 不一致。
## 下一步
### ⭐ 当前进度与待办(2026-10-05)
### ⭐ 当前进度与待办(2026-10-05 第二轮)
**本轮:`monte_carlo_probability` 已由用户写完并改到第二版**(L1,S 档)。Codex 两轮都只做工程侧,
**算法逻辑一行未动**(唯一例外是一个纯 typo:`z_score^2` → `z_score**2`,见易错点 21 / 决策 50)。
**本轮:`monte_carlo_integral` 已由用户写完并审查通过**(L1,S 档)。Codex 只改了一处**纯 bug**
(体积 `lows - highs` → `highs - lows`,见决策 51)—— **算法结构一行未动**。
**接下来三件事**
1. `monte_carlo_probability` 还剩 **2 条待办**(下面一节)——都是你的活。
1. `monte_carlo_integral` 有 **3 条待办**(下面一节)——都是你的活。
2. `ci_low` / `ci_high` 的语义确认一句(也是你的活),确认后 Codex 补 `docs/algorithms/monte_carlo.md` 卡片。
3. L0 最后一项 `save_figure`(A 档 = 工程,`viz/export.py`)—— **Codex 直接实现**,你只读。
**你下一个要写的(S 档,仍在 `solve/simulate.py`)**:`monte_carlo_integral` → `random_walk`。
`monte_carlo_integral` 其实就是 `monte_carlo` 的特例:自己造 `n×d` 的均匀样本 → 逐点求值 →
`mean(f) × 体积`,正好把那套"造批 → 一次算 → 汇总"的写法复用第二次。
**你下一个要写的(S 档,仍在 `solve/simulate.py`)**:`random_walk`。
它和刚写完的两个函数是**同一套骨架的第三次复用**(造批 → 一次算 → 汇总),新东西只有两处:
`np.cumsum` 把增量累成轨迹;`data` 的 shape 从 `(n,)` 变成 `(n_paths, n_steps + 1)` 的**二维**。
### ✅ monte_carlo_probability 的 6 条待办(2026-10-05 已办 5 条)
### ✅ monte_carlo_probability 的 6 条待办(2026-10-05 已办 5 条、剩 1 条)
- ~~① `p <= 1e-3` 判 `success=False`~~ → 改成 `warning_message`,仍返回区间(实测 p=0 → `(0.0, 0.00383)`)。
- ~~② Wald 在 p 靠近 1 时退化~~ → 换 **Wilson**(p=1、n=1000:`(1.0, 1.0)` → `(0.99617, 1.0)`)。
@@ -443,10 +468,22 @@ docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向
- ~~⑤ `metrics` 混 `np.float64`~~ → `float()` 统一(实测 4 个 key 全是 `float`)。
- ~~⑥ `n: int`~~ → 已改 `int | np.integer`。
**还剩 2 条(你的活)**
**还剩 1 条(你的活)**
1. `event: Callable[..., bool]` 的注解与行为不符:实际必须吃**整批**参数、返回长度 `n` 的数组。
2. 警告文案把 **p ≈ 1** 也说成"小概率事件 P < 1e-3 不建议使用"——那一条建议对 p→1 是错的
(临界情形该说的是"改用 1−p 或换方法")。条件是你写的 `p_value <= 1e-3 or p_value >= 1 - 1e-3`,文案只覆盖了前一半。
改法(一行):`Callable[..., bool]` → `Callable[..., Any]`,与同模块的 `sampler` / `simulator` 对齐。
pyright 实测:`Callable[..., bool]` 会**拒绝**一个注解良好的向量化 `event`(返回 `ndarray` 与 `bool` 不兼容),
`Callable[..., Any]` 才收得下 —— 这正是"注解与行为不符"的实际代价。
### ⏳ monte_carlo_integral 的 3 条待办(2026-10-05,你的活)
1. **`result.message` 被无条件覆盖**:拿到 `result` 后直接 `result.message = "…成功…"`,内层 `success=False` 时也照写。
实测 `func` 返回标量时,对外是 `success=False` + **"蒙特卡洛多重积分成功"** —— 两个字段自相矛盾。
方向(你定):用 `if result.success:` 包住,或 message 只**追加**、不覆盖。
2. **`bounds` 形状没校验**:`bounds=[0, 1]`(1-D 扁平)在 `bounds_arr[:, 0]` 抛 `IndexError: too many indices`
—— 是裸异常,没包进 `Result`。方向:要求"每个元素都是二元组",入口给可读的 `ValueError`;
顺手也能和 `(1, 0)` 那条校验合并到一处写。
3. **`n: int = 10000` 与全模块不一致**:别的函数都写 `int | np.integer`(§4.7)。
运行期其实收 `np.int64`(`monte_carlo` 里放行了),只是注解更窄 —— 调用方按注解老实写,pyright 会对 `np.int64` 报错。
### ⏳ monte_carlo 只剩 1 个口径要你确认
@@ -461,7 +498,7 @@ docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向
- B:改成均值 CI → 写"估计值 1.0024 ± 0.0043(95% CI)"(证明 n 够大)
- C:两者都要 → 指标要改名(如 `p025`/`p975` + `mean_ci_low`/`mean_ci_high`)
注意:`monte_carlo_probability` 里的 `ci_*` 已经是**估计量的 CI**(Wald 区间),所以这两个函数的 `ci_*`
注意:`monte_carlo_probability` 里的 `ci_*` 已经是**估计量的 CI**(Wilson 区间,决策 50),所以这两个函数的 `ci_*`
目前**含义不同**——这正是要给它们定口径的原因之一。
### ✅ 已拍板(决策 48)