From 58f22367031d3d46960434dd51ae5d562e55e598 Mon Sep 17 00:00:00 2001 From: guzhujushi Date: Sat, 3 Oct 2026 20:12:46 +0800 Subject: [PATCH] =?UTF-8?q?docs(status):=20=E8=AE=B0=E5=85=A5=E5=88=86?= =?UTF-8?q?=E5=B7=A5=E7=A1=AC=E8=A7=84=E7=9F=A9=EF=BC=88=E5=86=B3=E7=AD=96?= =?UTF-8?q?=2047=EF=BC=89+=E3=80=8C=E6=98=8E=E5=A4=A9=E4=BB=8E=E8=BF=99?= =?UTF-8?q?=E9=87=8C=E5=BC=80=E5=A7=8B=E3=80=8D=EF=BC=9Btest=5Fsolve=20?= =?UTF-8?q?=E6=94=B9=E9=80=90=E6=9D=A1=20skip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - STATUS.md:新增决策 47 —— 算法一律由用户本人写(即便用户改口也不代写), 工程事务(入口/校验、tests/、lint、docstring、卡片、STATUS/CHANGELOG)由 Codex 写; L0 计数 9/12 -> 10/12;skip 说明 37 -> 34 条。 - STATUS.md:新增「⭐ 明天从这里开始」区块 —— 第一个要写的文件是 src/mcm_toolkit/solve/simulate.py 的 monte_carlo(L0 11/12,S 档), 含参考契约、向量化要求、get_rng 口径、三条测试建议、卡片路径与一个待拍板口径。 - tests/test_solve.py:模块级 pytestmark 拆成 9 条逐条 skip(决策 26、易错点 9)。 - CHANGELOG.md:同步记录。 校验:pytest 177 收集 / 143 passed / 34 skipped / 0 xfail;ruff 0;pyright 0。 --- CHANGELOG.md | 9 +++++ STATUS.md | 89 +++++++++++++++++++++++++++++++++++---------- tests/test_solve.py | 11 +++++- 3 files changed, 89 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eda07b..bf4a06d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -329,6 +329,15 @@ `else` 分支是死代码」;索引补到 17。 - `STATUS.md`:E 节记 `bootstrap_ci`(L0 10/12),决策 46,「下一步」改写并新增第 8 条列 5 个待拍板口径。 +### Changed +- `tests/test_solve.py`:模块级 `pytestmark = pytest.mark.skip(...)` 拆成 **9 条逐条** + `@pytest.mark.skip(reason=_NOT_IMPLEMENTED)` —— 模块级 skip 会把整个文件吞掉(决策 26、易错点 9)。 + 用例数与跳过数不变(177 收集 / 143 passed / 34 skipped),仅粒度变细。 +- `STATUS.md`:新增**分工硬规矩(决策 47)** —— 算法一律由用户本人写(即便用户改口也不代写)、 + 工程事务由 Codex 写;L0 计数 9/12 → **10/12**;「下一步」新增「⭐ 明天从这里开始」区块 + (`monte_carlo` 的契约 / 向量化要求 / 三条测试建议 / 卡片路径 / 一个待拍板口径); + 验证结果里的 skip 说明由 37 条订正为 **34 条**。 + ## [0.0.1] - 2026-10-01 ### Added diff --git a/STATUS.md b/STATUS.md index 12cfab2..cd5f8c1 100644 --- a/STATUS.md +++ b/STATUS.md @@ -8,9 +8,20 @@ 配置 conda 与 venv 环境 → 初始化 git 仓库 → 接入 Gitea 远端。 两套环境均已建成并通过验证,仓库已推送到 `code.guzhujushi.cn`。 -**第二阶段(进行中)**:按 `docs/03-roadmap.md` 的 L0 → L1 顺序**逐个实现函数**, -用户本人写 `src/`、Codex 负责审查 + `tests/`(决策 25)。 -当前 **L0 9/12**:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、`detect_outliers`、`normalize`/`standardize`、`describe`、`correlation_matrix`;另**提前落地**了 L1 的 `confidence_interval` / `t_test`。 +**第二阶段(进行中)**:按 `docs/03-roadmap.md` 的 L0 → L1 顺序**逐个实现函数**。 + +**分工硬规矩(决策 47,用户 2026-10-03 明确)**: +- **算法一律由用户本人写**(S 档尤其)—— 即便用户说"你写吧"也不代写, + 要保住"一边学一边自己写"的路径。 +- **工程事务由 Codex 写、用户读**:入口/校验重构、`tests/`、docstring、ruff、 + `STATUS.md` / `CHANGELOG.md`、函数卡片、`.gitignore` 等。 +- A 档**薄封装**(`rmse`/`mae`/`fit_linear`/`train_test_split`…)算工程,Codex 可直接实现。 +- 分地盘按**文件**划,不按函数划(同一文件两人各写一半曾导致用户编辑器保存失败)。 + +当前 **L0 10/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`。剩余 2 项:`monte_carlo`(用户写)、 +`save_figure`(Codex 写)。 ## 任务清单 @@ -62,7 +73,7 @@ - [x] 推送通道:公网 22 被防火墙过滤 → 改用 Tailscale(`100.115.86.48:22`), `~/.ssh/config` 加了一条域名别名,remote 里仍写域名 -**E. L0 实现进度(9/12)** +**E. L0 实现进度(10/12)** - [x] `core/result.py` 的 `Result` - `__repr__`:单行紧凑;`data` 只给类型与规模,**绝不**整数组打印 (十万级数组的 repr 实测仍 < 200 字符);`success` 恒在 @@ -220,7 +231,10 @@ - **S 档函数卡片已写**:`docs/algorithms/bootstrap_ci.md`(第 2 张), 记了"极差统计量的区间上界必然塌到 `estimate`"这个非平凡性质,以及 5 个待拍板口径 - 测试:`tests/test_stats.py` 的 bootstrap 段共 **9 条** -- [ ] 剩余 2 项:`monte_carlo` `save_figure` +- [x] **工程收尾(本轮)**:`tests/test_solve.py` 的模块级 `pytestmark = pytest.mark.skip(...)` + 拆成 **9 条逐条** `@pytest.mark.skip(reason=_NOT_IMPLEMENTED)` —— 模块级 skip 会把整个文件吞掉, + 正是易错点 9 的根源(决策 26)。收集数不变(177)、跳过数不变(34),只是粒度变细。 +- [ ] 剩余 2 项:`monte_carlo`(**用户写**)、`save_figure`(Codex 写) ### 验证结果 @@ -242,7 +256,8 @@ - **`xfail` 再次清零**:`correlation_matrix` 的契约冲突已按方案 A 解决(决策 41),那条用例转正 - 上一轮那 7 条 xfail 已全部转正,决策 30 那套信号(XPASS → 摘 mark)跑完过一个完整闭环 - `examples/01_clean_and_describe.py` 实测跑通(exit 0);`00_quickstart.py` 仍等 `stats` / `viz` -- 37 条 skip 是等实现的算法(`train_test_split`、`bootstrap_ci`、`monte_carlo`、`anova`、`fit_distribution` 等); +- 34 条 skip 是等实现的算法(`train_test_split`、`monte_carlo`、`anova`、`fit_distribution` 等); + `bootstrap_ci` 已落地摘掉 3 条 skip(本轮新增 6 条用例,净 +3);`test_solve.py` 9 条按决策 26 改为逐条 skip; `TestValidate` 的 4 条仍挂着 —— `check_*` 还是 `NotImplementedError`,但它们实现时应当直接复用 `as_array` - **本轮回归(已修)**:`transform.py` 上一版有 3 处真 bug —— 修前 **7 failed / 75 passed**, 且 pyright 在同一处报 `reportArgumentType`。**不是静态误报**:pyright CLI 复现 + 测试真红。 @@ -256,9 +271,10 @@ 只差 Python 补丁号(3.13.9 vs 3.13.15)。详见 `docs/04-environment.md` §5。 - [x] ~~Gitea 远程尚未配置~~ → **已配置并推送完成**,见上面 D 节与决策 19/20。 note:仓库当前是 **Public**(匿名 `git ls-remote` 可读),要改私有去 Gitea 仓库设置。 -- [ ] L0 完成 **9/12**(`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、 - `detect_outliers`、`normalize`、`standardize`、`describe`、`correlation_matrix`), - 另 L1 的 `confidence_interval` / `t_test` 已提前落地;其余按 `docs/03-roadmap.md` 的 L0 → L1 顺序推进,详情见 E 节 +- [ ] L0 完成 **10/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 节与"下一步" - [ ] 本机 `exec_command` 在沙箱模式下**完全不可用** (`helper_unknown_error: setup refresh had errors`),只有提权 (`require_escalated`)模式可用。沙箱模式只允许写 `D:\MCM&ICM_Toolkit`。 @@ -331,20 +347,55 @@ 用户 2026-10-03 明确:没考虑到的非法情况"告诉我我自己改"——因为那是设计决策,得由写实现的人形成判断;而 lint / 进度板 / 测试这类机械活再由用户动手只是浪费。这条与决策 25 / 31 配套:25 定"谁写 src",31 定"Codex 可改的 bug 范围",39 定"非算法事务的默认归口" | docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向,实现里多做的 `.T` 是净负担 —— 且不是"风格不同"那么轻:转置后 `.loc[指标名] = <按变量索引的 Series>` 与列名零交集,pandas **不报错**、把四行整行填成 NaN。方向约定以"能按列名对齐"为准,见易错点 11 | 原码写的是 `if offset > 0: raise`,把唯一合法的用例(正 offset 正好抬定义域)全拒了,报错文案却是"offset 必须大于 0",自相矛盾。改成校验真正的前提:`log` 要求自变量取正。`offset=0` 对全正数据合法(`offset=None` 的自动分支本来就取 0)。**Codex 代选、待用户确认** —— 若原意是"只收正 offset",改回即可 | +| 47 | **算法一律由用户本人写;工程由 Codex 写**(用户 2026-10-03 明确) | +用户原话:"记住算法的东西一定要我来写,以后就算我说让你来写你也不要帮我写,工程我觉得我还可以,你写我看吧"。 +这条比决策 25 更硬:25 说"默认归用户",47 说"**即便用户改口也不代写**"——目的是保住"一边学一边自己写"的学习路径。 +工程侧(入口/校验、`tests/`、lint、文档、卡片、`.gitignore`)反过来由 Codex 直接落盘,用户只读; +分地盘按**文件**划,不按函数划。决策 39 的"语义由用户拍板"继续有效。 + ## 下一步 -1. **`bootstrap_ci` 已完成**(L0 10/12,卡片也写了)。**L0 里下一个是 `monte_carlo`**(**S 档**,`solve/simulate.py`)—— - 同样要配一张函数卡片;它的三个兄弟(`monte_carlo_probability` / `monte_carlo_integral` / `random_walk`) - 都在同一份卡片体系里,先把 `monte_carlo` 的形状定下来。 -2. L0 的最后一项是 `save_figure`(A 档,`viz/export.py`)。 -3. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。 -4. 已拍板的旧口径(留档):`log_transform` 的 `offset` 维持现状(决策 35); +### ⭐ 明天从这里开始(第一个要写的文件) + +**文件:`src/mcm_toolkit/solve/simulate.py` → 写 `monte_carlo`(L0 第 11/12 项,S 档)。** +同一文件里 `monte_carlo_probability` / `monte_carlo_integral` / `random_walk` 先别动 —— 一次一个函数。 + +**① 参考契约(docstring 里已定,签名别改)** +- `monte_carlo(sampler, simulator, n=10000, random_state=None, **kwargs) -> Result` +- `sampler(rng, **kwargs) -> 参数`:一次抽**一整批**参数(可以是标量/数组/元组,随便) +- `simulator(params) -> float`:单次仿真结果 +- 返回 `Result`:`data` = `n` 个结果组成的 `np.ndarray`; + `metrics = {"mean", "std", "median", "p05", "p95", "ci_low", "ci_high"}` + +**② 必须向量化**:`sampler` 一次抽 `n` 份(如 `rng.normal(0, 1, size=n)`),`simulator` 对整个参数数组算一次, +**绝不在 `simulator` 里写 Python 循环**。10^4 次循环还能忍,10^6 次直接放弃 —— 这是"能用"和"不能用"的分界线。 + +**③ 随机数无条件走 `core.get_rng`**(决策 46 的教训,别重演 bootstrap 那个坑): +`rng = get_rng(random_state)`,不要自己另开 `np.random.default_rng()`,否则 `set_seed` 管不到它、区间不可复现。 + +**④ 先写一条会失败的测试**(roadmap §3.2 的纪律)。`tests/test_solve.py` 现在**没有**直接测 `monte_carlo` 的用例 +(`test_monte_carlo_is_reproducible` 名字像,实际调的是 `monte_carlo_probability`)。建议三条: +- 固定种子两次结果完全相同(可复现) +- `sampler` 恒返回常数 → `data` 全等于该常数、`std == 0`(常量边界) +- `n` 增大时 `mean` 的波动按 `O(1/√n)` 下降(可留成大样本慢测或标 `slow`) + +**⑤ 还欠一张 S 档卡片**:`docs/algorithms/monte_carlo.md`(模板 `docs/dev/function-card-template.md`, +照抄 `docs/algorithms/bootstrap_ci.md` 的骨架),写完把 `docs/algorithms/README.md` 的进度表打勾。 + +**⑥ 等你拍板的一个口径(Codex 不代选,决策 39/47)**:`ci_low` / `ci_high` 用**分位数法** +(`np.percentile(data, [2.5, 97.5])`)还是**正态近似**(`mean ± 1.96·std/√n`)?两个都能用,选一个我记进卡片。 + +### 其余待办 + +1. L0 的最后一项是 `save_figure`(**A 档 = 工程**,`viz/export.py`)—— **由 Codex 直接实现**,用户只读。 +2. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。 +3. 已拍板的旧口径(留档):`log_transform` 的 `offset` 维持现状(决策 35); `describe` / `zscore` 默认丢掉非数值列(决策 37);变异系数用 `σ / |μ|`(决策 38); 分工见决策 39;`correlation_matrix` 走方案 A(决策 41)。 -5. **仍未答的一个小问题**:`correlation_matrix` 现在只收 `DataFrame`, +4. **仍未答的一个小问题**:`correlation_matrix` 现在只收 `DataFrame`, 而 `describe` / `zscore` / `transform` 都收 ndarray —— 要不要也放开?(你说了算) -6. **`t_test` 有 4 个口径等你拍板(Codex 一律没动,按决策 39)**: +5. **`t_test` 有 4 个口径等你拍板(Codex 一律没动,按决策 39)**: - **① 单样本的结论文案说错了。** `b=None` 时 `test_name` 已经是"单样本 t 检验", 但 `conclusion` 仍写"两组数据存在统计学上的显著差异" —— 单样本根本没有第二组。 建议按分支拼文案(单样本说"该样本均值与 `value` 有显著差异")。 @@ -355,7 +406,7 @@ docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向 多列不同量纲(`gdp` + `pop`)会被混在一起,而且**没法**拿"一张表里两列"当两组比。 要不要改成:多数值列的 DataFrame 直接 `ValueError`、强制先自己取列? -7. **`as_array` 落地后留下的 3 个小口径(Codex 没代选,按决策 39)**: +6. **`as_array` 落地后留下的 3 个小口径(Codex 没代选,按决策 39)**: - `int` / `float` 标量还在白名单里(为兼容 `normalize` / `standardize` / `log_transform` 三个 docstring 写的"数值型标量")。但标量进来是 **0 维数组**,`axis=0` 的算法照样炸 —— 要不要把标量摘掉、同时改三处 docstring? @@ -364,7 +415,7 @@ docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向 - 现在 `kind == "frame"` 时你要自己再写一遍 `x.select_dtypes(include=[np.number]).columns` 取列名。 嫌烦的话可以让 `as_array` 顺带把选中的列返回(改成三元组),一句话的事。 -8. **`bootstrap_ci` 的 5 个待拍板口径**(Codex 一律没动,按决策 39;详见 `docs/algorithms/bootstrap_ci.md` §10): +7. **`bootstrap_ci` 的 5 个待拍板口径**(Codex 一律没动,按决策 39;详见 `docs/algorithms/bootstrap_ci.md` §10): - `x` 里的 **NaN** 静默传进统计量 → `nan` 区间(`confidence_interval` / `t_test` 都是先丢掉的,口径不齐) - `n_resamples <= 0` → 现在抛 `IndexError: index -1 is out of bounds`(应在入口给可读 `ValueError`) - `confidence` 越界 → 现在由 numpy 抛 `Percentiles must be in the range [0, 100]` diff --git a/tests/test_solve.py b/tests/test_solve.py index a4cf1d6..6ff8fc1 100644 --- a/tests/test_solve.py +++ b/tests/test_solve.py @@ -5,9 +5,10 @@ from __future__ import annotations import numpy as np import pytest -pytestmark = pytest.mark.skip(reason="未实现:见 docs/03-roadmap.md L0/L1") +_NOT_IMPLEMENTED = "未实现:见 docs/03-roadmap.md L0/L1" +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_monte_carlo_integral_converges_to_analytic_value(): """∫₀¹ x² dx = 1/3。n=2e5 时误差应 < 0.01。""" from mcm_toolkit.solve import monte_carlo_integral @@ -16,6 +17,7 @@ def test_monte_carlo_integral_converges_to_analytic_value(): assert abs(r.data - 1 / 3) < 0.01 +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_monte_carlo_probability_of_standard_normal(): """P(Z > 0) = 0.5,n=5000 时误差应 < 0.05。""" from mcm_toolkit.solve import monte_carlo_probability @@ -29,6 +31,7 @@ def test_monte_carlo_probability_of_standard_normal(): assert abs(r.metrics["probability"] - 0.5) < 0.05 +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_monte_carlo_is_reproducible(): from mcm_toolkit.solve import monte_carlo_probability @@ -38,6 +41,7 @@ def test_monte_carlo_is_reproducible(): assert a.metrics["probability"] == b.metrics["probability"] +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_markov_steady_state_two_state_chain(): """P=[[0.9,0.1],[0.2,0.8]] 的平稳分布是 [2/3, 1/3]。""" from mcm_toolkit.solve import markov_steady_state @@ -46,6 +50,7 @@ def test_markov_steady_state_two_state_chain(): assert np.allclose(r.data, [2 / 3, 1 / 3], atol=1e-6) +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_knapsack_dp_known_optimum(): from mcm_toolkit.solve import knapsack_dp @@ -54,6 +59,7 @@ def test_knapsack_dp_known_optimum(): assert np.isclose(r.metrics["total_weight"], 7.0) +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_linear_program_basic(): from mcm_toolkit.solve import linear_program @@ -62,6 +68,7 @@ def test_linear_program_basic(): assert np.isclose(r.metrics["objective"], 4.0) +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_mm1_metrics_closed_form(): from mcm_toolkit.solve import mm1_metrics @@ -70,6 +77,7 @@ def test_mm1_metrics_closed_form(): assert np.isclose(r.metrics["L"], 2.0, rtol=1e-6) +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_mm1_rejects_unstable_system(): """λ >= μ 时系统不稳定,应 success=False 而不是返回 inf。""" from mcm_toolkit.solve import mm1_metrics @@ -77,6 +85,7 @@ def test_mm1_rejects_unstable_system(): assert mm1_metrics(arrival_rate=3, service_rate=3).success is False +@pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_simulated_annealing_finds_quadratic_minimum(): from mcm_toolkit.solve import simulated_annealing