feat(solve): 审查 markov_simulate:工程侧收口 + 9 条测试
用户实现的第一版 markov_simulate 算法本体经探针实测正确(终点频率 [0.657, 0.343] 对照平稳分布 [2/3, 1/3],同种子可复现),本次只动工程侧,未改任何算法行: - ruff 8 条全清(I001 导入排序 + 5 处 W293 / 2 处 W291) - docstring 从一行补全为 Parameters / Returns / Raises / Notes / Examples - 文案:马尔科夫→马尔可夫(3 处)、initail→initial、半角逗号→全角、paths[:,0]→paths[:, 0] - arr, kind = as_array(...) 的 kind 全程未用 → arr, _ - tests/test_solve.py 新增 9 条 markov_simulate 测试(此前 0 条) - STATUS.md 新增决策 56(initial 语义待用户拍板,A/B/C 三选项)与 5 条待办 - CHANGELOG 与 docs/dev/personal-pitfalls.md 易错点 27(入口分流后两条分支口径矛盾) 校验:pytest 188 passed / 31 skipped(219 收集);ruff 0;pyright 0
This commit is contained in:
1 parent
9368dd91cb
commit
dd9bd638b8
5 files changed
+323
-23
No files matched your search
@@ -21,9 +21,12 @@
|
||||
当前 **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`、`monte_carlo_integral`、`random_walk`(**用户写**,2026-10-05)。剩余 1 项:`save_figure`(Codex 写)。
|
||||
`monte_carlo` 的口径已拍板(决策 48);`monte_carlo_probability` / `random_walk` 的待办**均已办结**,
|
||||
剩下 `ci_low`/`ci_high` 的语义、`monte_carlo_integral` 的 1 条口径(第一道 `isinstance` 分支已冗余),见"下一步"。
|
||||
`monte_carlo_probability`、`monte_carlo_integral`、`random_walk`、`markov_simulate`
|
||||
(**均为用户写**,2026-10-05)。剩余 1 项:`save_figure`(Codex 写)。
|
||||
`monte_carlo` 的口径已拍板(决策 48);`monte_carlo_probability` / `random_walk` 的待办**均已办结**;
|
||||
`markov_simulate` 本轮审查通过(算法本体正确),留 **5 条入口口径待你拍板**(见"下一步",决策 56 的 3 个选项)。
|
||||
另 3 个小口径仍悬着:`ci_low`/`ci_high` 的语义、`monte_carlo_integral` 的第一道 `isinstance` 分支可塌成两行、
|
||||
`random_state` 注解与 `std` 的 `ddof` 是否全项目对齐。
|
||||
|
||||
## 任务清单
|
||||
|
||||
@@ -317,13 +320,28 @@
|
||||
- **第三轮(2026-10-05)**:`start` 校验恢复、注解放宽为 `int | np.integer`;最后一条"方阵歧义"用户选**方案 B**,
|
||||
`step_sampler` 契约改为"整数 `size=n_paths * n_steps` + 必须返回一维 + 内部行主序 `reshape`"(决策 55,Codex 代改)
|
||||
|
||||
- [x] `solve/simulate.py` 的 `markov_simulate`(**L1,S 档,算法由用户本人写**,2026-10-05)
|
||||
- 核心:`arr[current_states]` 取当前状态那一行 → `cumsum` 得累积分布 → 用 `[0, 1)` 均匀数"落格"
|
||||
(`(cum_probs < r[:, None]).sum(axis=1)`)→ 写进下一列。`data` 形状 `(n_paths, steps + 1)`,值是整数状态编号
|
||||
- **Codex 审查结论:算法本体正确**(探针实测 `(2000, 201)`、状态 ∈ {{0,1}}、200 步后终点频率
|
||||
`[0.657, 0.343]` 对照平稳分布 `[2/3, 1/3]`、同种子可复现)
|
||||
- **Codex 只碰工程侧**:① ruff 8 条全清(`I001` 导入排序 + 5 处 `W293` / 2 处 `W291` 空白);
|
||||
② `arr, kind = as_array(...)` 的 `kind` 全程未用 → 改 `arr, _`;
|
||||
③ docstring 从一行补到完整的 `Parameters` / `Returns` / `Raises` / `Notes` / `Examples`;
|
||||
④ 文案:`马尔科夫` → `马尔可夫`(3 处)、`initail` → `initial`、半角逗号 → 全角;`paths[:,0]` → `paths[:, 0]`
|
||||
- **测试:新增 9 条**(此前 `markov_simulate` 一条都没有)—— 形状 / 首列 / 状态取值 / 整数 dtype、
|
||||
单位矩阵吸收态自锁、可复现(同种子相同、异种子不同)、终点频率收敛到 `[2/3, 1/3]`、
|
||||
metrics = 终点状态、非法转移矩阵(非方阵 / 行和不为 1)、非法 `steps` / `n_paths`(0 / float / bool)、
|
||||
`np.int64` 可收、docstring Example 真能跑
|
||||
- ⏳ **留给你的 5 条**见"下一步 → markov_simulate 待你拍板"(全是入口口径与少考虑情况,决策 49)
|
||||
|
||||
- [ ] 剩余 1 项:`save_figure`(A 档 = 工程,**Codex 直接写**)
|
||||
|
||||
### 验证结果
|
||||
|
||||
| 检查 | venv | conda `mcm-toolkit` |
|
||||
|---|---|---|
|
||||
| `pytest` | 见下(单列) | **179 passed, 31 skipped(0 xfail)** |
|
||||
| `pytest` | 见下(单列) | **188 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` | 跑通 | — |
|
||||
@@ -333,14 +351,15 @@
|
||||
(修配置之前是 17 errors,全是"无法解析导入 numpy / pandas",见决策 29 与
|
||||
`docs/04-environment.md` 坑 7)
|
||||
- base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致)
|
||||
- **210 条用例:179 passed / 31 skipped / 0 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
|
||||
`test_core.py` 23、`test_solve.py` 43(本轮 +1:采样器契约"整数 `size` + 行主序")、`test_viz.py` 8、`test_decision.py` 7、
|
||||
- **219 条用例:188 passed / 31 skipped / 0 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
|
||||
`test_core.py` 23、`test_solve.py` 51(本轮 +9:`markov_simulate` 从 0 条起步)、`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`
|
||||
- 31 条 skip 是等实现的算法(`train_test_split`、`random_walk`、`markov_*`、`anova`、`fit_distribution` 等);
|
||||
`monte_carlo_probability` / `monte_carlo_integral` 已落地,分别摘掉 2 条与 1 条 skip;
|
||||
- 31 条 skip 是等实现的算法(`train_test_split`、`markov_steady_state`、`mm1_metrics`、`anova`、`fit_distribution`、
|
||||
`knapsack_dp`、`linear_program`、`simulated_annealing` 等);
|
||||
`monte_carlo_probability` / `monte_carlo_integral` 已落地,分别摘掉 2 条与 1 条 skip;`markov_simulate` 新增的 9 条全部实跑;
|
||||
`TestValidate` 的 4 条仍挂着 —— `check_*` 还是 `NotImplementedError`,但它们实现时应当直接复用 `as_array`
|
||||
- **本轮回归(已修)**:`transform.py` 上一版有 3 处真 bug —— 修前 **7 failed / 75 passed**,
|
||||
且 pyright 在同一处报 `reportArgumentType`。**不是静态误报**:pyright CLI 复现 + 测试真红。
|
||||
@@ -499,22 +518,47 @@ docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向
|
||||
歧义原样回来。**代价(已写进 docstring)**:`step_sampler` 不能再返回二维,步与路径的对应关系是**行主序**。
|
||||
探针实测:默认采样器 `(5, 101)` 正常;后四种(转置二维 / 方阵二维 / 长度 +1 / `(N, 1)`)一律判失败。
|
||||
|
||||
| 56 | `markov_simulate` 的 `initial` 语义**待用户拍板**(Codex 只报选项,未代改,决策 49) |
|
||||
现状用 `isinstance(initial, int)` 区分"标量状态 / 状态数组",两条分支的口径互相打架:标量分支按**路径数**铺满
|
||||
(`np.full(n_paths, initial)`),数组分支却按**状态数**校验长度(`len(initial) != s` 直接报错)——
|
||||
于是数组分支只有 `n_paths == s` 时才可能跑通;而 `paths[:, 0] = current_states` 又写在 `try` **外面**,
|
||||
长度不匹配时**裸抛** `ValueError`(本该 `success=False`,同易错点 26 一族)。另有三个口子:
|
||||
`np.int64(0)` 掉进数组分支、被 `as_array` 拒(与 `steps`/`n_paths` 收 `np.integer` 不一致,易错点 1 复发);
|
||||
`True` 因 `isinstance(True, int)` 为真被当状态 1 **静默放行**;越界状态(如 `initial=5`、`s=2`)只在
|
||||
第一轮索引时才炸、被 `except` 吞成 `success=False`。可选口径见下一步的 A / B / C。
|
||||
|
||||
## 下一步
|
||||
|
||||
### ⭐ 当前进度与待办(2026-10-05 第四轮)
|
||||
### ⭐ 当前进度与待办(2026-10-05 第五轮)
|
||||
|
||||
**本轮:`random_walk` 第二版(用户按 4 条待办改定)已审查通过**。Codex 又抓到一处**真 bug**:
|
||||
`call_params` 定义在 `try` 内、却在 `except` 里被引用,**所有失败路径**都抛 `UnboundLocalError`(易错点 26,见决策 54);
|
||||
另补 docstring、+3 条测试(共 9 条)。
|
||||
**本轮:`markov_simulate`(你写的第一版)已审查通过** —— 算法本体正确,没动你任何算法行;
|
||||
Codex 只清工程侧(ruff 8 条、docstring、3 处 `马尔科夫`、`initail` 拼写、半角逗号、`kind` 未用),
|
||||
并补 **9 条测试**(此前 0 条)。三连:`pytest` **188 passed / 31 skipped**、`ruff` 0、`pyright` 0。
|
||||
|
||||
**接下来三件事**
|
||||
1. `random_walk` 的 7 条待办**全部办结**(4 条 + 本轮 3 条),下面一节留档。
|
||||
2. `ci_low` / `ci_high` 的语义确认一句(也是你的活),确认后 Codex 补 `docs/algorithms/monte_carlo.md` 卡片。
|
||||
1. **`markov_simulate` 的 5 条待你拍板**(下节,全是入口口径,决策 49/56)。
|
||||
2. `ci_low` / `ci_high` 的语义确认一句(A / B / C 三个选项在下面挂着),确认后 Codex 补 `docs/algorithms/monte_carlo.md` 卡片。
|
||||
3. L0 最后一项 `save_figure`(A 档 = 工程,`viz/export.py`)—— **Codex 直接实现**,你只读。
|
||||
|
||||
**你下一个要写的(S 档,仍在 `solve/simulate.py`)**:`markov_simulate` → `markov_steady_state`。
|
||||
两者是同一件事的两面:前者按转移矩阵**逐代抽状态**(`rng.choice` 的 `p=` 版),后者用**迭代**求平稳分布。
|
||||
`markov_steady_state` 的 docstring 已写明口径:用迭代而不用特征分解,因为"收敛快不快"本身就是论文里要写的性质。
|
||||
### ⏳ markov_simulate 待你拍板(5 条,Codex 只报不改)
|
||||
|
||||
**① `initial` 的语义(最大的一条,三个选项)**
|
||||
现在标量分支按**路径数**铺满、数组分支按**状态数**校验长度,两条口径互相矛盾(详见决策 56)。
|
||||
- **A**:只收标量状态,另加 `initial_dist` 参数显式传"初始分布"(分布再抽一次,语义最干净,签名变长)
|
||||
- **B**:只收长度 `n_paths` 的数组(标量走 `np.full` 铺满),`data` 首列就是给定的初始状态序列(与 `paths[:, 0]` 赋值天然一致)
|
||||
- **C**:沿用现状,但把类型收成 `int | np.integer` + 先判 `bool`,把 `paths[:, 0] = ...` 挪进 `try`,docstring 写清"数组按路径数"
|
||||
|
||||
**② 转移矩阵没查非负**:`[[1.5, -0.5], [0, 1]]` 行和恰为 1.0 → 通过校验,但 `cumsum` 非单调 →
|
||||
落格结果**全是 0**(静默错)。建议入口加一条 `np.any(arr < 0)` → `ValueError`。
|
||||
**③ `paths[:, 0]` 赋值在 `try` 之外**:长度不匹配时裸抛 `ValueError`,与"出错统一 `success=False`"(决策 48)不一致。
|
||||
**④ 状态越界没在入口拦**:`initial=5`(`s=2`)第一轮才 `IndexError`,被吞成 `success=False`;
|
||||
建议入口加 `0 <= initial < s`。
|
||||
**⑤ `std_terminal` 对**分类**状态的意义**:状态编号是标称量,均值和标准差只对"有序状态"(如排队人数)成立 ——
|
||||
要不要把 `metrics` 换成状态频率(`terminal_freq`)?你定。
|
||||
|
||||
**你下一个要写的(S 档,仍在 `solve/simulate.py`)**:`markov_steady_state`。
|
||||
与 `markov_simulate` 是同一件事的两面:前者按转移矩阵逐代抽状态,后者用**迭代**求平稳分布。
|
||||
docstring 已写明口径:用迭代而不用特征分解,因为"收敛快不快"本身就是论文里要写的性质。
|
||||
|
||||
### ✅ random_walk 的 4 条待办(2026-10-05 已办结)
|
||||
|
||||
|
||||
Reference in new issue
Block a user