用户本轮改了 markov_simulate / markov_steady_state 的入口校验,并写完 mm1_metrics,
三处算法本体都正确(mm1 的 5 个公式经探针与闭式解逐位对拍通过,含 W = Wq + 1/mu)。
Codex 只动工程侧:
Fixed
- mm1_metrics 的稳定性分界 "<" 应为 "<="(docstring 与既有测试的口径都是 lambda >= mu 不稳定):
lambda == mu 原先掉进 try、rho == 1.0 触发 ZeroDivisionError,被 except 吞成 success=False。
两个分支的可观测输出完全相同(success=False / metrics={} / data=None),只有 message 不同,
故按决策 31 直接修并加回归测试,未改动任何口径。
- 三处报错文案的参数名抄错:markov_steady_state 的 max_iter <= 0 写成 "tol 必须是正数",
mm1_metrics 的 service_rate 两条报错都写成 "tol";半角逗号统一为全角。
Changed
- mm1_metrics 的 docstring 补 Raises 与 data 说明;call_params = {...} 补空格;
ruff 6 条(W293 x5 / F541 f-string 无占位符 / W292 文件末尾无换行)。
- markov_steady_state 的 try 前 3 个空行 -> 1;"异常: " -> "异常:"。
- tests/test_solve.py:摘掉 2 条 mm1_metrics 的 skip,新增 4 条(lambda == mu 的回归、
非负校验回归、max_iter=3 未收敛判 success=False)。
- STATUS.md 决策 56 / 57 更新办结项 + 新增决策 58(mm1 的 4 条待拍板);CHANGELOG;易错点 29。
校验:pytest 202 passed / 28 skipped(230 收集);ruff 0;pyright 0
81 KiB
STATUS — 当前进度
更新日期:2026-10-05
本次任务
第一阶段(已完成):审查并精简 ChatGPT 的建库建议 → 生成骨架文件 →
配置 conda 与 venv 环境 → 初始化 git 仓库 → 接入 Gitea 远端。
两套环境均已建成并通过验证,仓库已推送到 code.guzhujushi.cn。
第二阶段(进行中):按 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 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、markov_simulate、
markov_steady_state、mm1_metrics(均为用户写)—— solve 层的 L1 清单已全部落地。
剩余 1 项:save_figure(Codex 写)。
monte_carlo 的口径已拍板(决策 48);monte_carlo_probability / random_walk 的待办均已办结;
markov_simulate(决策 56)/ markov_steady_state(决策 57)/ mm1_metrics(决策 58)
仍有一批入口口径待你拍板(本轮各办结了一批,详见"下一步")。
另 3 个小口径仍悬着:ci_low/ci_high 的语义、monte_carlo_integral 的第一道 isinstance 分支可塌成两行、
random_state 注解与 std 的 ddof 是否全项目对齐。
任务清单
✅ 已完成
A. 建议审查与精简
- 通读
chatGPT_Suggestion.md(33 节,约 2.4 万字),输出docs/01-suggestion-review.md- 总裁决:方向对,量级错
- 14 个包 → 7 个;约 250 个函数 → 约 40 个;5 个 Result 类 → 1 个
- 单函数文档要求 10 项 → 2 档(只有约 15 个招牌算法写完整卡片)
- 逐项给出"砍什么 / 为什么 / 替代什么",并补上原文缺失的六件事 (论文产出链路、指标方向与量纲、时间预算、可复现性、测试测什么、离线约束)
B. 骨架文件生成(76 个文件,约 6800 行)
docs/02-architecture.md架构、docs/03-roadmap.md路线图与永久不做清单docs/04-environment.md环境文档、docs/dev/function-card-template.md卡片模板src/mcm_toolkit/7 个包 23 个模块(只有签名 + docstring +NotImplementedError)tests/9 个文件、examples/4 个、problems/notebooks/figures/tables/pyproject.toml(核心依赖 4 个 + 6 组 extras)、environment.yml.gitignore.gitattributes.editorconfigLICENSECHANGELOG.mdREADME.md- 脚本
scripts/setup-venv.ps1setup-conda.ps1doctor.ps1
C. 环境配置
- venv 已建好:
.venv(10.5 MB,Anaconda base +--system-site-packages) +pip install -e . --no-deps --no-build-isolation; 可直接导入 numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6 / matplotlib 3.11.2 / sklearn 1.9.1 / statsmodels 0.15.0 / networkx 3.6.1 / sympy 1.14.0 (版本随 base 走,base 升级后自动同步) - conda 环境已建好:
D:\anaconda\envs\mcm-toolkit(1.55 GB,Python 3.13.15) - 科学栈:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6 / matplotlib 3.11.2 / sklearn 1.9.1 / statsmodels 0.15.0 / networkx 3.6.1 / sympy 1.14.0(MKL 后端) - 已补装 pip-only 依赖:SALib、pymoo 0.6.2、pyright 1.1.414 - 已在本项目中可编辑安装 - 实测耗时 868 秒,下载约 3 GB - Anaconda base 已升级 2025.12 → 2026.07(365 个包,下载 638 MB,耗时 1033 秒)
- conda 自身 25.11.1 → 26.7.3(实测复核)
- 并把项目环境 numpy 对齐到 base 的 2.4.6,两套环境数值栈现已完全一致(MKL 后端)
- 升级前已用
conda list -n base --revisions留了回滚点 - 环境脚本按实测结果重写:
setup-conda.ps1默认改为精准包列表 (--clone base实测会走完整下载安装,不再是"离线快捷方式")
D. Git 仓库
git init -b main,core.autocrlf=false,首次提交5319589(76 文件 / 6812 行),工作区干净git ls-files --eol复核:全部 LF,.gitattributes生效- 仓库由本人账户 guzhu 创建(见决策 17)
- 远端 Gitea 已接通并推送:
origin=git@code.guzhujushi.cn:guzhujushi/MCM-ICM_Toolkit.git,main跟踪origin/main,远端 HEAD =96080b9,76 个文件已在服务器上(见决策 19/20) - 推送通道:公网 22 被防火墙过滤 → 改用 Tailscale(
100.115.86.48:22),~/.ssh/config加了一条域名别名,remote 里仍写域名
E. L0 实现进度(10/12)
-
core/result.py的Result-__repr__:单行紧凑;data只给类型与规模,绝不整数组打印 (十万级数组的 repr 实测仍 < 200 字符);success恒在 -summary():Markdown 表格,可直接贴进论文; 行序success→params.*→metrics.*→extras.*; float 走.6g,|转义,message以引用行附在表后;幂等无副作用 - 测试:tests/test_core.py去掉 skip + 新增 3 条 (默认值/失败路径/大数组、Markdown 结构、可重复调用) -
core/random.py的get_rng/set_seed(设计由用户本人写,Codex 补齐类型校验) - 无参get_rng()复用同一个模块级生成器且会推进:既保证重跑一致, 又避免两个都省略random_state的函数抽到同一串数(原决策 24 的取舍已消掉) -get_rng收int/np.integer/Generator/None,其余类型抛TypeError:np.int64不是int的子类,曾因此静默退回全局流 -set_seed同时调np.random.seed,覆盖 sklearn / statsmodels 走的 numpy 旧全局 (全项目唯一碰旧接口的地方,带# noqa: NPY002与理由) - 测试:TestRandom共 7 条(可复现、透传、无参跟随、旧全局、np.integer、非法类型、共享推进) -
data/io.py的load_table(+save_table,原属 L1,顺手落地) - 实现由用户本人写(决策 25),Codex 只做审查 + 补测试 -.csv/.xlsx/.xls按扩展名分派;.csv默认编码探测 utf-8 → utf-8-sig → gbk → gb18030,显式传encoding=时不再探测 -na_values默认吃下空串 /NA/N/A/#N/A/null/-- 不支持的扩展名抛ValueError;文件不存在抛带路径的FileNotFoundError-save_table的校验顺序已修正:后缀判断提到mkdir之前 (此前传.txt会先留下一个空目录再抛错) - 测试:tests/test_data.py由 1 条(且被模块级 skip 掉)扩到 9 条 - docstring 的Parameters/Raises已按 §4.7 补齐(Codex 代写,用户授权)—— numpydoc 格式,对齐参考实现core/random.py的get_rng -
data/clean.py的clean_missing(L0 第 4 项) - 实现由用户本人写,Codex 只做审查、补测试与文档 -threshold用math.ceil算min_non_na:3 列、阈值 0.5 时必须是 2, 写int()截断会漏删 - 顺手落地兄弟函数impute_missing/winsorize(原属 L1) -
data/clean.py的detect_outliers(L0 第 5 项,S 档,实现由用户本人写) - 语义由用户本人定:非数值列不参与统计,掩码里恒False(见决策 32) - 三轮修掉的 4 个 bug:threshold范围校验放错函数、IQR 上界符号、is_series/is_ld_array判在类型转换之后(永远为 False)、文本列上算分位数直接崩 - 关键教训:限制掩码 ≠ 限制统计量 —— 只把mask[numeric_cols] = ...收窄是不够的,quantile()/mean()/std()也要一起收窄(见坑 8 / 个人易错点 8) - 返回形状与输入同形;Series 进 Series 出(列名保留)、一维数组进一维布尔数组出 - S 档函数卡片已写:docs/algorithms/detect_outliers.md(含"为什么不返回Result"、非数值列恒False的两条备选路、ddof口径不一致等) - 测试:tests/test_data.py该函数共 9 条 -
data/clean.py的两处收尾(Codex 代写,用户授权) -impute_missing的 mode 分支改为返回新表(原先就地改调用方的表,与另外四支不一致) —— 最后一条xfail随之转正,xfail清零 -detect_outliers补入参类型校验(§4.7 要求非法类型抛TypeError): 接受DataFrame/Series/list/tuple/ndarray,dict不再接受(决策 33) -
examples/01_clean_and_describe.py:load_table/save_table/clean_missing/impute_missing/detect_outliers/winsorize六个函数的最小用法,实测可直接跑通 -
data/transform.py的四个函数(由用户本人实现):normalize/standardize(L0 第 6、7 项)、log_transform/one_hot_encode(原属 L1,顺手写了) -normalize:三种 method(minmax/vector/max)都按列;feature_range校验下界 < 上界; 常量列压到下界而不出 nan(决策 34) -standardize:按列,ddof真的传下去 —— 测试用解析关系钉住 (ddof=0→ std 恰为 1;ddof=1→sqrt((n-1)/n)= 0.866) -log_transform:offset=None时自动取1 - min(x),保证x + offset >= 1-one_hot_encode:自动识别的 dtype 集合已显式补上'str'/'string'(见坑 9) - Codex 清掉 5 条 ruff(RET503 / F541 / RET504 / RET505 / SIM108),并把feature_range的两条校验提到np.asarray之前、修掉说反的报错文案(条件是"下界必须小于上界") - 测试:新写 18 条,其中两条是把原先挂着的skip转为实跑 —— 见tests/test_data.py的 transform 段 -
transform.py的 4 个口径已收口(2026-10-02,Codex 代修,用户授权"报错顺手修掉"): - 输入白名单统一成模块级_NUMERIC_TYPES,DataFrame补进去并移到np.asarray之前 (此前DataFrame不在白名单 →standardize(df)/log_transform(df)直接TypeError, 函数体里所有is_df分支都是死代码) - 三个函数都保持"DataFrame 进 DataFrame 出"(列名 + 索引原样带回),Series 进 Series 出 -normalize的三个 method 分支原来互相覆盖(minmax/vector算完的结果被末尾 无条件的x / max冲掉,三种 method 实际都只走 max),改成if / elif / else-one_hot_encode不再拿data.columns重包一层(哑变量列名对不上 → 编码列整列丢失) -log_transform的offset守卫写反了(offset > 0就抛错,恰好拒掉唯一合法用例), 改成校验真正的定义域前提min(x) + offset > 0(见决策 35) - docstring 按 §4.7 补齐Parameters/Returns/Raises(Codex 代写,与io.py同一套 numpydoc 格式) - 口径定稿后 ruff 4 条 RET505 一并清掉 →All checks passed;pyright 2 errors → 0 -
stats/descriptive.py的describe(L0 第 8 项,由用户本人实现,Codex 审查 + 代修) - 一个静默 bug(最危险的那种):df_num.describe().T把表转反了(行 = 变量、列 = 指标), 而 docstring 与test_describe_reports_expected_keys都要求"行 = 指标、列 = 变量"。 转置后.loc[指标名] = <按变量索引的 Series>与列名零交集,pandas 不报错, 直接把这四行(missing/missing_ratio/skewness/kurtosis)整行填成 NaN。 去掉.T一处即修好三件事:方向对了、NaN 没了、weights分支不再报cannot set a row with mismatched columns(见易错点 11、决策 36) -median行补上(pandas 的describe()只有50%,而测试契约定的是median) - 入参放宽到list/tuple/ndarray(兄弟函数都收,00_quickstart.py传的就是 ndarray) -weights校验补齐:长度、非一维、NaN/inf、负值、和为 0 都抛带原因的错误 (原先和为 0 时裸抛ZeroDivisionError) -zscore:修 RET505,docstring 补Parameters/Returns/Raises;coefficient_of_variation:均值为 0 由TypeError改为ValueError(那是取值问题不是类型问题)、 空数组提前报错(原先静默返回 nan + RuntimeWarning)、改掉"异变系数"错别字;分母改用|mean|(用户本人定,见决策 38) - pyright 的DataFrame | Series重载问题按坑 8 用cast收窄;ruff 7 条清到 0 - 测试:test_stats.py去掉模块级pytestmark(它把 6 条测试全部跳过了 —— 决策 26 说的就是这个,实现写完了却零覆盖),改成逐条 skip 未实现的 5 条, 并新写 20 条 describe / zscore / 变异系数用例 -
examples/01_clean_and_describe.py:填掉末尾的TODO(L0),加一段describe输出 -
stats/inference.py的correlation_matrix(L0 第 9 项,由用户本人实现,Codex 审查 + 代修) - 硬 bug:if numeric_cols == 0:拿Index和标量比,得到的是布尔数组 —— 0 列时静默不报错、1 列时碰巧能跑、≥2 列直接ValueError崩; 而"≥2 个数值列"正是最常见的情况,等于整个函数不可用。改成len(numeric_cols) == 0(易错点 13) - pyright 的报错也都不是运行时问题:data[numeric_cols]是坑 8 的DataFrame | Series重载 (cast收窄);_, p = stats.pearsonr(...)解包会退化成object—— scipy 的返回值是_make_tuple_bunch运行时拼的类、存根里看不到pvalue(见docs/04-environment.md坑 10)。 加了_pvalue(result: Any)小助手统一取 p 值,一处都没挂# type: ignore-method校验提到最前、清掉 3 条 F541 / 1 条 RET505 / I001,ruff 归零 - 现在返回:correlation_matrix→ 数值系数矩阵(对角线 1.0);correlation_pvalues→ 数值 p 值矩阵(对角线 NaN);correlation_table→ 星标字符串表(对角线1.000不带星)。 - 返回契约冲突(已解决):上一版返回的是带星标的字符串表(1.000***), 而 docstring 契约与 L0 用例都要求数值矩阵(对角 = 1.0);按方案 A 拆成三个函数(见下) -
correlation_matrix的返回契约按方案 A 落地(2026-10-03,用户拍板):correlation_matrix只返回数值矩阵;星标表拆成correlation_table、p 值拆成correlation_pvalues(三者共用_check_corr_args守卫与成对删缺失口径,见决策 41)。 顺带修掉两处:对角线不再带***(p 值矩阵的对角线是 NaN、"自己和自己"没有显著性); p 值与 r 用同一批观测(都成对删缺失、都尊重min_periods),不再各算各的样本量。 p 值只算上三角再镜像,省一半重复计算。测试 +8 条(含用解析式t = r·sqrt((n-2)/(1-r²))对照) -
stats/inference.py的confidence_interval/t_test(L1 提前落地,实现由用户本人写,Codex 审查 + 代修) - 用户问的「307/308 两行报错」不是真错:那两行是float(res.statistic)/float(res.pvalue), 报错来自 scipy 的TtestResult—— 它和PearsonRResult一样是_make_tuple_bunch运行时拼的类, 存根里没有这两个字段。探针实测TtestResult(statistic=3.873, pvalue=0.0305),运行时取值完全正常。 按决策 40 的路子补了一个_statistic(result: Any) -> float助手(与_pvalue成对), 一行# type: ignore都没挂。详见docs/04-environment.md坑 10 -confidence_interval有真 bug(已修):原来返回(float(min(0.0, lower)), float(max(upper, 1.0)))——min/max的参数写反(下界被钉死成0.0),而且无条件把所有 method 的区间夹到[0, 1]。 实测confidence_interval(rng.normal(170, 8, 50), method="t")的下界是0.0(正确是171.0 ± 2.1);confidence=1.5更是被夹成(0.0, nan)而不报错。现在只有proportion夹(决策 42),normal/t直接返回mean ± z|t·se;method非法改抛ValueError(§4.7)、confidence按(0, 1)开区间校验、样本量 < 2 抛ValueError-confidence_interval的入参放宽到list/tuple/ndarray/Series/DataFrame: docstring 的 Examples 传 list、L0 用例传 ndarray,原来两条都直接TypeError-t_test:_clean_data改用isinstance(data, pd.DataFrame)收窄(坑 8 的cast)、alpha开区间校验、df按 Welch–Satterthwaite / pooled / paired 分别给,docstring 补齐Parameters/Returns/Raises/Notes(§4.7) - 测试tests/test_stats.py:+10 条(36 → 46),含把原先挂着 skip 的test_confidence_interval_matches_scipy转正; 对照一律用解析式(z 公式、Welchdf公式、t = (mean − value)/(s/√n)),不拿实现对自己 - ⚠️t_test还有 3 个口径待用户拍板(Codex 未改,见「下一步」第 6 条) -
core/validate.py的as_array(统一入口,2026-10-03 落地) - 用户提出"每个函数都抄一遍入口/校验/交换太麻烦"。结论:入口能统一,出口不能一刀切 —— 还原要求"输出与输入同形",而describe/correlation_*/confidence_interval/t_test的输出形状天生和输入不同,硬套只会重演易错点 14。 - 契约:as_array(x, *, numeric_only=True, dtype=float, ndim=None) -> (arr, kind),kind是"frame"/"series"/"array"(只报告原类型,还原由调用方手写,用户拍的板)。 - 覆盖掉重复:原先select_dtypes/isinstance(..., pd.DataFrame|Series)散在 5 个文件 31 处; 现在normalize/standardize/log_transform/zscore/confidence_interval/t_test各只剩 1 行入口。data/transform.py的_NUMERIC_TYPES白名单连同 3 份副本一起删掉。 - 两条硬规矩:默认绝不摊平(要摊平显式ndim=1);不删缺失值(删缺失是各函数的语义, 见决策 45 与易错点 15)。numeric_only按"输出是否与输入同形"选,不按喜好选(决策 44)。 -check_array(L1,未实现)实现时直接复用as_array,一份实现两张脸 -
stats/bootstrap.py的bootstrap_ci(L0 第 10 项,S 档,实现由用户本人写,Codex 审查 + 代修) - 移植正确性已用解析解钉住:大样本正态下百分位区间半宽 ≈2 × 1.96 × se(容差 10%) - Codex 代修的三处: ①random_state=None时原来另开了np.random.default_rng(),绕过了get_rng—— 与决策 24 冲突(set_seed后仍然不可复现)。改成无条件的get_rng(random_state); ②statistic_name写成hasattr(statistic, "__name__"),而 lambda 的__name__恒为"<lambda>", 导致else分支是死代码、自定义统计量拿不到"custom"(见易错点 17); ③ ruff 两条(I001 导入块、F541 空 f-string),并把 docstring 的Parameters/Returns/Raises补齐(§4.7) - 测试是重点:用户实现写完后,tests/test_stats.py里等它的 3 条仍然挂着skip, 全量照样显示"全绿" —— 正是易错点 9 的复发(已把该条复发记到 1)。 已摘掉 3 条 skip,并新增 6 条:estimate等于原样本统计量、confidence单调性、 五种容器一致、自定义 lambda 走回退分支、不给种子时跟随set_seed、非法method抛错。 - S 档函数卡片已写:docs/algorithms/bootstrap_ci.md(第 2 张), 记了"极差统计量的区间上界必然塌到estimate"这个非平凡性质,以及 5 个待拍板口径 - 测试:tests/test_stats.py的 bootstrap 段共 9 条 -
工程收尾(本轮):
tests/test_solve.py的模块级pytestmark = pytest.mark.skip(...)拆成 9 条逐条@pytest.mark.skip(reason=_NOT_IMPLEMENTED)—— 模块级 skip 会把整个文件吞掉, 正是易错点 9 的根源(决策 26)。收集数不变(177)、跳过数不变(34),只是粒度变细。 -
solve/simulate.py的monte_carlo(L0 第 11 项,S 档,算法由用户本人写,2026-10-04) - 核心逻辑:向量化采样 → 一次仿真 → 8 个指标;get_rng无条件调用(决策 46 已内化) - 用户第二轮改了三处(见决策 48):批量大小改由实现传sampler(rng, size=n); 结果含 NaN 判失败;np.std(data, ddof=0)写明 - Codex 只碰工程侧,算法一行未动: ① ruff 三条(I001 合并同源 import、F541 空 f-string ×2) ② docstring:补Parameters(原文漏random_state/**kwargs)与Raises;按新契约改写sampler说明(必须能接住size,且**kwargs里别再传size);Notes记 NaN 口径; 修掉跑不通的 Examples(原版lambda rng, **k: rng.normal(0, 1)返回标量,实测success=False) ③ 按 §4.7 收严n校验:np.int64不再被拒、n=True改抛TypeError、浮点n归TypeError、n<=0归ValueError④ 格式与文案:success= True→success=True;失败文案"重采样" → "模拟"(从bootstrap_ci抄来没改) ⑤ 入口统一dtype=float—— 修掉一个真 bug:布尔结果("事件是否发生",MC 最常见的用法) 会让np.std在 numpy 2 里抛boolean subtract ... is not supported,整个函数判失败; 字符串结果也从"ufunc isnan 不支持"变成可读的could not convert string to float- 测试:10 条(tests/test_solve.py)—— 返回长度与 8 个 key、simulator只被调用一次(向量化的可执行版本)、 同种子可复现、省略种子时跟随set_seed、E[X²]=1(n=2e5)、docstring Example 真能跑、np.int64的 n、 布尔结果按 0/1 统计、NaN 结果判失败、5 个非法入参 - 行为探针实测:simulator调用次数 = 1(真向量化);20/20 次区间覆盖 0 -
solve/simulate.py的monte_carlo_probability(L1,S 档,算法由用户本人写,2026-10-05) - 核心:向量化event(batch) -> bool 数组→p = mean(data)→ Wilson score 区间并夹到[0, 1](比例量纲,沿用决策 42 的口径——同类问题没重犯) - 用户第二轮(2026-10-05)按 6 条待办改掉 5 条:Wilson 替 Wald、callable入口校验、confidence记进params、float()统一 ci 类型、小概率不再判失败(改成 message 里的警告)——见决策 50 - Codex 第三轮只碰工程侧:①z_score^2→z_score**2—— 真 bug:^是异或, numpy 浮点直接抛TypeError,被except吞成success=False,6 条测试全红才暴露(易错点 21); ② message 无警告时的尾逗号;③{"confidence":confidence}的空格;④ docstring 的 "Wald" → "Wilson"; ⑤ 新增 1 条对照测试:区间必须等于statsmodels.proportion_confint(method="wilson")- 行为探针实测:p≈0.5 正确(0.49991 @ n=2e5)、confidence变大区间变宽、 同种子可复现、省略种子跟随set_seed、np.int64的 n 可收、confidence=1.5抛ValueError- Codex 只碰工程侧,算法一行未动: ① ruff 六条(SIM300 Yoda、W291 ×2、W293 ×2、F541) ② docstring:补Parameters(原文只有 Returns/Notes/Examples)与Raises, 修掉跑不通的 Examples(原lambda rng, **k: rng.normal(0, 1)返回标量,实测success=False), 并把"Wald 区间 + 夹到 [0,1]"与"p <= 1e-3判失败"两条现状如实写进 Returns / Notes ③ 格式:metrics字典缩进、{"n":n, ...}空格、行尾空白 ④ 文案:NaN 提示里的 "simulator" 改成 "event"(从monte_carlo抄来没改) - 测试:摘掉 2 条 skip(test_monte_carlo_probability_of_standard_normal、test_monte_carlo_is_reproducible—— 实现写完还挂着 skip,正是易错点 9)并新增 5 条,solve段共 24 条 -
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(名字打错 + 减反)。 奇数维体积为负:1Dx²于[0,1]实测 -0.3324(应 +1/3)、3Dx得 -0.5005(应 +1/2); 而 2Dx·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 条待办已由用户第二轮办结(同日):message改成if result.success:/else:两支(失败语义不再被覆盖); 入口补isinstance(bounds, (list, tuple))+ndim == 2 and shape[1] == 2两道([0, 1]从裸IndexError变成可读ValueError);n注解放宽为int | np.integer。⏳ 尚有 2 条新口径(bounds收不收 ndarray、if/else三行重复)见"下一步" -
solve/simulate.py的random_walk(L1,S 档,算法由用户本人写,2026-10-05) - 核心:step_sampler(rng, size=(n_paths, n_steps))一次抽完整批增量 →np.cumsum(..., axis=1)→ 首列填start、其余列start + cumsum。data形状(n_paths, n_steps + 1)- Codex 改了一处真 bug(决策 31,纯 bug):默认采样器写rng.integers(0, 2, size=size, dtype=float), 而 numpy 的integers不收dtype=float(TypeError: Unsupported dtype dtype('float64') for integers) —— 默认路径 100% 失败(success=False、data=None),连 docstring 的 Examples 都跑不通。 改成rng.integers(0, 2, size=size) * 2 - 1(后面的np.asarray(..., dtype=float)负责转 float)。 实测:(5, 101)形状、diff恒为 ±1、同种子可复现 - Codex 只碰工程侧:① ruff 两处 W293(只有空格的行);② docstring 补全(原只有step_sampler一节, 现补齐n_steps/n_paths/start/random_state+Returns/Raises/Notes, 并写清step_sampler收的是二维size=(n_paths, n_steps));③ Examples 增加一条"步长恒为 ±1"的断言 - 测试:新增 6 条(此前random_walk一条测试都没有)—— 形状与start列、默认步长恒 ±1、 同种子可复现、自定义高斯采样器(带漂移)、3 类非法入参、docstring Example 真能跑 - 用户第二轮(2026-10-05)按 4 条待办改定:n_steps/n_paths拆成TypeError(类型)/ValueError(取值); 加steps.shape != (n_paths, n_steps)与 NaN 两条校验;默认采样器换成rng.choice([-1.0, 1.0], size=size);params补random_state;metrics由"抄 params"改成mean_terminal/std_terminal(终点位置,ddof=0) - Codex 第二轮又抓到一处真 bug(决策 31):用户把call_params = {...}写进了try里,而except分支要引用它 —— 5 种失败输入 5 个全炸UnboundLocalError(本该返回success=False的Result,变成把异常抛给调用方)。 按原意把call_params移到try之前(不动任何口径),实测失败路径全部恢复正常;新增一条"必然失败"的回归测试(易错点 26) - 测试:本轮再 +3 → 共 9 条(新增"失败也必须返回 Result 且带 params"、NaN 判失败、√t扩散);start="a"那条恢复成pytest.raises(TypeError)(用户恢复了入口校验,见下) - 第三轮(2026-10-05):start校验恢复、注解放宽为int | np.integer;最后一条"方阵歧义"用户选方案 B,step_sampler契约改为"整数size=n_paths * n_steps+ 必须返回一维 + 内部行主序reshape"(决策 55,Codex 代改) -
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 真能跑 - 第二轮(2026-10-09):用户补了np.any(arr < 0)非负校验 → 待办 ② 办结; Codex 补一条"行和是 1 但含负元素"的回归测试 - ⏳ 余下 4 条见"下一步 → markov_simulate 待你拍板"(全是入口口径与少考虑情况,决策 49) -
solve/simulate.py的markov_steady_state(L1,S 档,算法由用户本人写,2026-10-09) - 核心:v从均匀分布出发,v_next = v @ arr反复推进(行随机矩阵按行向量右乘), 直到max|v_next - v| < tol或耗尽max_iter;data是平稳分布向量,metrics={"converged", "iterations"}- Codex 审查结论:算法本体正确(探针实测)—— 2 状态[2/3, 1/3](58 次迭代)、3 状态与np.linalg.eig(P.T)特征值 1 对应的特征向量逐位一致(39 次)、随机 6 状态一致(13 次)、200×200 一致到1.9e-12(6 次、0.10s)、 与markov_simulate的终点频率互验[0.6702, 0.3298]vs[0.6667, 0.3333]、sum(data) = 1.0- Codex 只碰工程侧:① ruff 5 条W293全清,mm1_metrics前 4 个空行 → 2; ② docstring 补max_iter/tol两个参数(原来 Parameters 里只有transition)、补Raises, 写清tol是相邻差口径(实测慢混合链tol=1e-10→ 实际误差4.6e-9,被1/(1-|λ2|)放大); ③ 修正 Notes 里"周期链会不收敛"的说法(可约链的实情是"平稳分布不唯一、迭代法给离初值最近的那个"); ④ 失败文案失败:→失败:(与文件内其它函数统一) - 测试:+8 条、摘掉 1 条 skip(此前该函数只有 1 条挂着的 skip)—— 2 状态[2/3, 1/3]+converged/iterations、 3 状态与特征分解对拍、data是分布(和为 1 / 非负 /float64)、慢混合链迭代次数 > 100、np.int64的max_iter、非法转移矩阵、max_iter=0/tol=0、docstring Example 真能跑 - 第二轮(2026-10-09,用户改定):补非负校验(① 办结)、params记transition(⑥ 办结)、success=converged(选口径 B,④ 办结)、tol拆成TypeError/ValueError且先判bool(② 办结); Codex 顺手修文案(max_iter <= 0那条写成 "tol 必须是正数")与异常:→异常:, 并补 2 条测试(负元素矩阵、max_iter=3未收敛走success=False) - ⏳ 余下 3 条 + 1 个新口子见"下一步 → markov_steady_state 待你拍板"(决策 57) -
solve/simulate.py的mm1_metrics(L1,S 档,算法由用户本人写,2026-10-09) - 核心:闭式解ρ = λ/μ、L = ρ/(1-ρ)、Lq = ρ²/(1-ρ)、W = 1/(μ-λ)、Wq = W - 1/μ;λ >= μ走"系统不稳定"分支返回success=False- Codex 审查结论:公式全对(探针实测 3 组参数(2,3)/(1,4)/(0.5,1.0)与闭式公式逐位一致,W = Wq + 1/μ恒等式也成立) - Codex 修了一处边界 bug(决策 31,纯 bug):稳定性判断写成service_rate < arrival_rate, 而 docstring 与既有测试的口径都是λ >= μ—— 于是λ == μ掉进try、rho == 1.0→ZeroDivisionError被except吞成success=False(结果碰巧对,message 却是"计算失败:ZeroDivisionError")。 两支的可观测输出完全相同(success=False/metrics={}/data=None),改<=只换 message,故直接修并加回归测试 - Codex 只碰工程侧:① ruff 6 条(5 处W293+F541f-string 无占位符 +W292文件末尾无换行); ② 文案:service_rate的报错写成 "tol"、max_iter的也写成 "tol"(同一轮里 3 处参数名抄错)、半角逗号 → 全角; ③call_params={...}→call_params = {...};④ docstring 补Raises+ 说明data与metrics同值 - 测试:+6 条、摘掉 2 条 skip(此前只有 2 条挂着的 skip)—— 闭式公式逐项对拍(含W = Wq + 1/μ)、λ == μ与λ > μ都判失败且带"不稳定"文案、入口(0 / 负数 /bool/ 非数值)、λ == μ不能靠ZeroDivisionError落到失败分支、docstring Example - ⏳ 留给你的 4 条见"下一步 → mm1_metrics 待你拍板"(决策 58) -
剩余 1 项:
save_figure(A 档 = 工程,Codex 直接写)
验证结果
| 检查 | venv | conda mcm-toolkit |
|---|---|---|
pytest |
见下(单列) | 202 passed, 28 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 |
跑通 | — |
| SALib / pymoo / pyright | 缺失 | 已装 |
pyright(分析目标 = 工作区.venv):0 errors, 0 warnings (修配置之前是 17 errors,全是"无法解析导入 numpy / pandas",见决策 29 与docs/04-environment.md坑 7)- base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致)
- 230 条用例:202 passed / 28 skipped / 0 xfail。收集数按文件:
test_data.py52、test_api_surface.py20、test_core.py23、test_solve.py62(本轮 +4、再摘掉 2 条 skip)、test_viz.py8、test_decision.py7、test_stats.py53、test_models.py5 xfail再次清零:correlation_matrix的契约冲突已按方案 A 解决(决策 41),那条用例转正- 上一轮那 7 条 xfail 已全部转正,决策 30 那套信号(XPASS → 摘 mark)跑完过一个完整闭环
examples/01_clean_and_describe.py实测跑通(exit 0);00_quickstart.py仍等stats/viz- 28 条 skip 是等实现的算法(
train_test_split、anova、fit_distribution、knapsack_dp、linear_program、simulated_annealing等);monte_carlo_probability/monte_carlo_integral/markov_steady_state/mm1_metrics已落地,分别摘掉 2 / 1 / 1 / 2 条 skip;markov_simulate新增的 9 条全部实跑;TestValidate的 4 条仍挂着 ——check_*还是NotImplementedError,但它们实现时应当直接复用as_array - 本轮回归(已修):
transform.py上一版有 3 处真 bug —— 修前 7 failed / 75 passed, 且 pyright 在同一处报reportArgumentType。不是静态误报:pyright CLI 复现 + 测试真红。 结论:见到红线先跑 CLI 与pytest,不许直接挂# type: ignore(见个人易错点 10) - 三个
.ps1通过 PowerShell AST 语法解析
⚠️ 需要注意 / 遗留
两套环境版本不一致→ 已解决:base 升级到 2026.07 后, numpy/scipy/pandas/matplotlib/sklearn/statsmodels/networkx/sympy 版本完全一致, 只差 Python 补丁号(3.13.9 vs 3.13.15)。详见docs/04-environment.md§5。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、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。 - 副作用:沙箱通道(Node 子进程)以
CodexSandboxOffline账户运行, 它写出的文件 Owner 是沙箱账户而非guzhu。 实测guzhu对这些文件仍有 Modify 权限(继承自Authenticated Users), 读写不受影响;但 git 的 dubious ownership 检查会因此误报, 所以.git必须由本人账户创建(见决策 17)。 若以后再用沙箱通道写文件,注意同一问题。
决策记录
| # | 决策 | 理由 |
|---|---|---|
| 1 | 包数从 14 砍到 7 | regression/ml 重复;optimization/simulation/sensitivity 同族;dynamics/network 低频 → 降级为模块 |
| 2 | 只保留 1 个 Result |
第一版没有任何函数需要区分 5 种结果类型,属过早分类 |
| 3 | 函数清单从 ~250 砍到 ~40 | 4 天赛程的工具,不是 6 个月的框架;没在真题里用过三次的不写 |
| 4 | 删掉 maximize / 重复的 normalize / 13 个 plot_* 等 |
同义或一行转发;plot(kind=...) 替代 13 个函数 |
| 5 | 文档从"每函数 10 项"降到 2 档 | 10 项文档的写作成本是实现的 20 倍,是最可能导致项目停滞的一条 |
| 6 | 补上原文缺失的论文产出链路 save_table / mcm_style |
美赛是论文比赛;原文只给了一个 save_figure |
| 7 | 补上永久不做清单 | 原文用"建议不要",防不住膨胀;写成硬规则 |
| 8 | venv 用 --system-site-packages |
10.5 MB、离线可用;作为轻量开发与断网兜底 |
| 9 | conda 环境用精准包列表而非 --clone base |
实测 --clone 会重放 base 全套包并逐个下载(连 pandoc 都下),远慢于精准列表 |
| 10 | 生成的是签名 + docstring + NotImplementedError | 用户要自己读代码学算法;骨架定接口,实现留给学习过程 |
| 11 | .ps1 写成纯 ASCII(英文注释/输出),而非中文 |
Windows PowerShell 5.1 无 BOM 时按 GBK 解析 .ps1,中文直接导致语法错误;保留中文就必须加 BOM,违反 AGENTS.md |
| 12 | .ps1 用 LF 而非 CRLF |
遵守 AGENTS.md「shell 脚本 UTF-8 无 BOM + LF」;PS 5.1 对 LF 无问题 |
| 13 | chatGPT_Suggestion.md 保持原样 |
用户手写的输入文件,不属于本次产物,不做无谓改动 |
| 14 | base 升级用 conda update -n base --all,而不是装新 conda |
计划干净(365 更新 / 0 删除 / 0 冲突),且能保留既有的 lingbot-map 环境 |
| 15 | 项目环境 numpy 降到 base 的 2.4.6 | 升 base 的 numpy 会连带把 BLAS 从 MKL 换成 OpenBLAS;宁可降一个 minor,也不动整套线性代数后端 |
| 16 | 新增 .gitattributes 强制 LF |
仓库要给队友在 Windows/WSL/Linux 上 clone,不固定行尾会产生"整文件都变了"的假 diff |
| 17 | .git 由本人账户 guzhu 重建,不做 safe.directory 豁免 |
沙箱通道建的 .git Owner 是 CodexSandboxOffline,git 会报 dubious ownership。加全局豁免是把问题藏起来;直接以本人账户重建,仓库和远程(Gitea)身份一致 |
| 18 | core.autocrlf=false(仓库级) |
配合 .gitattributes 的 LF 策略,避免 Windows 上 checkout 又转成 CRLF |
| 19 | Gitea 的 SSH 走 Tailscale 内网,不在公网开 22 | 实测公网 8.145.36.108:22 超时(服务器侧过滤,本机出站 22 没被封:github.com:22 可通),而 Tailscale 100.115.86.48:22 可达且 git@ 认证直接过 Gitea。remote 里仍写域名,域名→内网 IP 的映射放在 ~/.ssh/config,换机只改一处 |
| 20 | 首次推送用 --force-with-lease 覆盖 Gitea 的 Initial commit |
远端那个提交是建仓时自动初始化产生的,与本地无共同祖先,普通 push 必被拒。推送前已把其中的 LICENSE 与手写简介并入本地 README.md,原提交仍留在本机对象库(1c683cf) |
| 21 | Result.__repr__ 不打印 data 内容,只给类型与规模 |
模拟轨迹动辄十万个数,repr 一旦刷屏,traceback / 调试器 / notebook 输出就全废了。要看数据请直接取 result.data |
| 22 | Result.summary() 的行序 = 字典插入顺序,不排序 |
比赛时"想先看哪个指标"自己决定键的插入顺序即可,工具不该替人排序 |
| 23 | set_seed 内部允许调用 np.random.seed(带 noqa) |
sklearn / statsmodels 默认走 numpy 旧全局状态,只记自己的 _GLOBAL_SEED 覆盖不到它们,"提交能重跑"就是假的。旧接口在这一个点上不是"取随机数"而是"设种子",性质不同 |
| 24 | get_rng(None) 复用同一个模块级生成器(会推进),不每次新建 |
用户本人提出,取代 Codex 原来"每次新建 default_rng(全局种子)"的写法——那样两个都省略 random_state 的函数会抽到完全相同的数。共享单例既保证重跑一致,又让流自然推进 |
| 25 | src/ 下的实现默认由用户本人写,Codex 只改 tests/ 与 docs/ |
目标是"一边学一边自己写"。曾出现 Codex 直接落盘 random.py、与用户编辑器缓冲区冲突导致保存失败。需要代写时用户会明说 |
| 26 | 未实现的算法用逐条 @pytest.mark.skip,禁用模块级 pytestmark |
模块级 skip 会把同文件里已经写好的函数一起跳过。本次 test_data.py 就这样让 load_table 零覆盖,直到有人手动数才被发现 |
| 30 | 已知待修的契约用 @pytest.mark.xfail(strict=False),不用 skip |
skip 会让"契约还没写"和"写了但实现有问题"混在一起,5 条 skip 曾把 clean.py 的 bug 全藏住。xfail 照样进报告,修好后自动变 XPASS,是"修好了"的信号,而且 XPASS 不失败,不会卡住提交 |
| 29 | 静态分析器钉死到工作区 .venv,并把 base 的 site-packages 用 .pth 链进去 |
pyright / Pylance 不认 pyvenv.cfg 的 include-system-site-packages,.venv 自己只有 pip;不钉的话它们会去 PATH 上找 python,本机 PATH 上那个是 Codex 运行时的 3.14 shim,没有科学栈,于是报"无法解析导入"。.pth 是唯一被 pyright 认的转发手段 |
| 28 | 不启用 ruff 的 preview 规则集 | E302(函数间要 2 个空行)在 ruff 0.16 里是 preview 规则,select = ["E"] 默认不包含它——所以少一个空行 ruff 照样报 "All checks passed"。行数空行得自己盯,别指望 lint |
| 34 | 常量列(max == min)不定区间也不崩:normalize 压到 feature_range 下界,standardize 给 0 |
除零必须有个确定的答案,不能出 nan / inf。给 0 与 sklearn 的 MinMaxScaler / StandardScaler 一致(sklearn 把常量列的 scale 设为 1,结果就是 0),换库不换结论。行为已由 test_normalize_constant_column_stays_finite 与 test_standardize_constant_column_stays_finite 钉住 |
| 33 | detect_outliers 只接受 DataFrame / Series / list / tuple / ndarray,dict 一律 TypeError |
§4.7 要求非法类型抛 TypeError 而非静默兜底。dict 以前能"碰巧"被 pd.DataFrame() 转成表,但那不是文档承诺的形状,静默接受会让调用方以为传什么都行;tuple 则补进文档与 is_ld_array,与 list 同等对待 |
| 32 | detect_outliers 对非数值列恒返回 False(既不跳过也不报错) |
用户本人在两条路里选的。跳过会让 docstring 里"与原数据同形"变成谎话(形状对不上,没法按位置对回原表);报错则让一张带 city 列的真题表直接不可用。False 的读法是"这一列没有离群点",对文本列是最不坏的解释,也让整表能一次跑完 |
| 31 | Codex 可直接改 src/ 的范围:纯 bug(不改接口与语义)、格式、死代码 |
决策 25 说实现归用户,但"能跑"是底线:winsorize(pd.Series) 因 select_dtypes 在 Series 上不存在而直接崩、测试变红,这种必须当场修。凡是要选一个语义的(文本列返回什么、mode 要不要就地改)一律留给用户本人,Codex 只把选项和影响列清楚 |
| 27 | base 里的 zstandard 残骸目录改名禁用,不删除、不重装 | 删掉不可逆;pip install zstandard 会给 base 塞一个 conda 记录里根本没有的包,下次 conda update --all 又要重新判断。改名后 import 变回正常 ModuleNotFoundError,pandas 自动跳过 |
| 35 | log_transform 的显式 offset 按定义域前提校验:min(x) + offset > 0 才放行 |
|
| 36 | describe 的返回表不转置:行 = 指标、列 = 变量 |
|
| 37 | 非数值列的处置按输出契约分,不按包分:describe / zscore 直接丢掉;detect_outliers 保留列、掩码恒 False |
|
两族函数的产出物不一样:describe / zscore 吐的是数值结果表,文本列塞不进去,丢掉才能得到一张干净、可直接贴论文的表;detect_outliers 吐的是与原表同形的掩码,必须能按位置对回原表,丢列就废了。"不统一"不是前后矛盾,是契约不同。用户 2026-10-03 拍板:丢掉更好 |
||
| 38 | coefficient_of_variation 用 `σ / |
mean |
均值为负时 σ/μ 会给出负值,容易被读成"波动更小",其实只是符号跟着均值走。取绝对值后 CV 恒非负。可比性仍只在均值远离 0 的比率尺度数据上成立(已写进 docstring 的 Notes) |
||
| 39 | 工作约定:语义 / 非法输入口径由用户拍板,Codex 只列选项与影响、不代选;ruff、STATUS.md、CHANGELOG.md、tests/、docstring 这类与算法无关的工程事务,Codex 顺手补掉 |
|
| 40 | scipy 的检验结果统一走 _pvalue(result: Any) -> float 助手取 p 值,不逐处挂 # type: ignore |
|
| 41 | correlation_matrix 只返回数值矩阵;星标表拆 correlation_table、p 值拆 correlation_pvalues |
|
用户 2026-10-03 选方案 A。数值矩阵能做的事(挑高相关对、喂聚类 / PCA、画热力图)字符串表都做不了,而反过来随时能格式化 —— 展示需求属于输出端,不该侵占返回值;一旦返回字符串,数值信息不可逆地丢失。三个函数共用 _check_corr_args 守卫与成对删缺失口径,correlation_table 在最外层调另外两个(计算型当主、展示型当辅)。见易错点 14 |
||
| 42 | confidence_interval 只有 proportion 夹到 [0, 1];normal / t 是均值区间,一律不夹 |
|
原码把三种 method 的区间无条件夹进 [0, 1],且 min / max 参数写反(见易错点 15),实测把身高均值区间下界压成 0.0。均值区间的量纲本来就不在 [0, 1] 里;只有比例 p ± z·√(p(1−p)/n) 的边界才有意义。Codex 代改、待用户确认 —— 若原意是"什么区间都夹",改回 _clip 一行即可 |
||
| 43 | scipy 检验结果的统计量也走 _statistic(result: Any) -> float 助手,与决策 40 的 _pvalue 成对 |
|
TtestResult 与 PearsonRResult 同源(_make_tuple_bunch 运行时拼类),存根里 .statistic / .pvalue 都没有。用户报的「307 / 308 行报错」就是这两处,不是真 bug(探针实测取值正常)。见 docs/04-environment.md 坑 10 |
||
| 44 | 所有收数值的函数统一从 core.validate.as_array 进;还原不代做,调用方按返回的 kind 自己手写 |
|
用户 2026-10-03 提"每个函数都抄一遍入口/校验/交换太麻烦"。能统一的只有入口:出口要求"输出与输入同形",而 describe / correlation_* / confidence_interval / t_test 天生不同形,硬套会重演易错点 14。用户明确"不要三个函数""转换回去自己手打",所以 as_array 只返回 (arr, kind),ArraySpec / restore 都不做。numeric_only 默认 True(用户拍的板),但按输出契约选:同形函数必须显式 False |
||
| 45 | as_array 不删缺失值;容器类型/内容转换失败统一抛 TypeError |
|
删缺失是语义(df.corr 成对删、describe 整列统计、paired=True 的 t 检验必须成对删),在入口层删掉就把行对应关系永久丢了 —— 这正是 t_test 待拍板的口径之一。错误口径统一为 TypeError 是Codex 代选、待用户确认:normalize(含文本列的表) 原先由 numpy 抛 ValueError,现在是 TypeError(内容转不成 float);describe 的 weights 同类情况目前仍抛 ValueError(决策 39 说不统一就列出来) |
||
| 46 | bootstrap_ci 的随机数无条件走 core.get_rng;statistic_name 用 getattr(f, "__name__", None) 再判 "<lambda>" |
|
原码 random_state=None 时另开 np.random.default_rng(),等于绕开决策 24(set_seed 对它无效,区间不可复现)。Codex 代改:get_rng(None) 本来就是设计好的"复用模块级生成器且推进",直接传下去即可。statistic_name 的 else "custom" 是死代码 —— lambda 也有 __name__(值恒为 "<lambda>"),实测拿不到 "custom",见易错点 17 |
||
PearsonRResult / SignificanceResult 是 _make_tuple_bunch 在运行时拼出来的类,存根里没有 pvalue 字段:解包会退化成 object、属性访问会报"未知"。运行时完全正常(实测三个函数返回的都是 np.float64)。收成 Any 是一处收窄,调用点仍然干净;# type: ignore 要挂在每一处,且会连带关掉该行后面的检查。见 docs/04-environment.md 坑 10 |
||
| 用户 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 的"语义由用户拍板"继续有效。 |
| 48 | monte_carlo 的四个口径由用户拍板:实现传 size=n;结果含 NaN 判失败;std 用 ddof=0;出错仍统一返回 success=False |
用户 2026-10-04 直接改在代码里。① 批量大小由实现传(sampler(rng, size=n, **kwargs)):调用方少写一次,
代价是每个 sampler 都必须能接住 size,且 **kwargs 里不能再传 size(会重复传参)—— 已写进 docstring。
② 结果里只要出现 NaN 就 success=False:MC 的 NaN 多来自除零 / 模型发散,静默统计只会得到一列 nan 指标。
③ np.std 默认 ddof=0,现在显式写明(与 describe 那条线的 ddof=1 不一致,属已知差异)。
④ 计算期异常仍全部包成 success=False(保留 try/except Exception);代价是自己写错代码时没有 traceback。
配套 Codex 代改:入口 np.asarray(..., dtype=float) —— 布尔结果此前会让 np.std 抛错(真 bug,已修)。
唯一未定:ci_low/ci_high 保持"结果的分位数"(现状)还是改成"均值的置信区间"。
| 49 | 这类改动归用户本人:入口没收紧、少考虑情况、不符合简单原则 —— 用户 2026-10-04 明确"交给我改,不要帮我改" |
用户原话:"以后像这种入口没收紧、少考虑情况、不符合简单原则的情况交给我改,不要帮我改,把这几条经验写入经验文档"。
这是决策 47 的收窄:47 把"入口 / 校验重构"划给了 Codex,49 把它收回 —— 凡是带判断、取舍、设计成分的,
一律由用户写,Codex 只报告 + 列选项(与决策 39 同一逻辑)。判据一句话:这处改动要不要"想一下才对" ——
要,就留给用户;不要(ruff、格式化、docstring 缺节、tests/、STATUS / CHANGELOG、函数卡片)才由 Codex 顺手补。
本轮实例(本该留给用户,Codex 已代改、用户可回退重写):monte_carlo 的 n 按 §4.7 收严;入口 np.asarray(..., dtype=float)。
经验已写入 docs/dev/personal-pitfalls.md 第 18 / 19 / 20 条,并把第 1 条(类型判断只写 int)复发 +1。
| 50 | monte_carlo_probability 的区间由 Wald 改 Wilson score;小概率不再判失败(改成 message 警告);confidence 记进 params |
用户 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") 对齐,已加成对照测试。
遗留(2026-10-05 第二轮复核,同日已清): → 已改 event: Callable[..., bool] 注解与"吃整批、返回数组"不符Callable[..., Any];
警告文案把 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 不一致。
| 52 | monte_carlo_integral 的入口口径由用户第二轮改定:message 只在成功分支改写;bounds 必须是非空 list/tuple 且形状 (d, 2);n 收 int \| np.integer |
用户 2026-10-05 按 Codex 报的 3 条自行改定。message 走 if result.success: / else:(失败分支保留失败语义并追加追溯信息:
维度 / 体积 / 模拟次数)—— 修掉"success=False + 一句成功文案"的自相矛盾。bounds 入口两道校验把
[0, 1] 从 IndexError: too many indices 变成带当前形状的可读 ValueError。遗留 2 条新口径:
① isinstance(bounds, (list, tuple)) 把 np.array([[0, 1]]) 也拒了,与"尽量传 ndarray"的偏好冲突;
② if / else 两支里 name / params["bounds"] / params["dimension"] 各写了一遍(易错点 20)。
| 53 | random_walk 的默认步长采样器不能用 rng.integers(..., dtype=float)(numpy 直接抛 TypeError);step_sampler 收到的 size 是二维 (n_paths, n_steps) |
用户 2026-10-05 写完第一版。默认采样器原写 rng.integers(0, 2, size=size, dtype=float) * 2.0 - 1.0 ——
Generator.integers 不收浮点 dtype(TypeError: Unsupported dtype dtype('float64') for integers),
于是整个默认路径 100% 失败,连 docstring 的 Examples 都跑不通;Codex 按决策 31 改成 rng.integers(0, 2, size=size) * 2 - 1
(交给后面的 np.asarray(..., dtype=float) 转类型),实测形状 (5, 101)、步长恒 ±1、同种子可复现。
step_sampler 的 size 是 (n_paths, n_steps) 而不是 monte_carlo 那样的一个整数 —— 契约已写进 docstring,
遗留 4 条待办见"下一步"(TypeError/ValueError 分工、steps 形状校验、NaN 口径、params 缺 random_state)。
| 54 | random_walk 第二轮口径由用户改定:n_steps/n_paths 类型错抛 TypeError、取值错抛 ValueError;加形状与 NaN 两条校验;默认步长改 rng.choice([-1.0, 1.0]);params 补 random_state;metrics 改 mean_terminal/std_terminal |
用户 2026-10-05 自行改定,Codex 只修了一处纯 bug:call_params 定义在 try 内、却被 except 引用,
导致所有失败路径抛 UnboundLocalError(实测 5/5 失败输入全中)。按原意前移到 try 之前,未改任何口径。
遗留 3 条:① start 的入口类型校验被删(start="a" 从 TypeError 变成 success=False),是有意还是顺手删的?
② 签名 n_steps: int / n_paths: int 与运行期收 np.int64 不一致(§4.7;monte_carlo_integral 的 n 已放宽);
③ n_paths == n_steps 时,返回转置形状的采样器仍会静默通过(形状校验天生发现不了,见易错点 25)。
| 55 | step_sampler 的"方阵歧义"按方案 B 解决:size 改成整数 n_paths * n_steps、必须返回一维数组,内部 reshape(n_paths, n_steps) |
形状校验(决策 54)对"方阵"天生失效 —— 转置 (n_steps, n_paths) 正好等于目标 (n_paths, n_steps)。
用户 2026-10-05 拍板选 B(Codex 代改,用户明确要求)。转置 (n_steps, n_paths) 与目标 (n_paths, n_steps) 在方阵时形状相同,
校验从形状上无法区分 —— 根源是契约本身有歧义(一个 tuple 尺寸给了两种朝向)。改成整数尺寸 + 要求一维后,
二维返回一律判失败,转置问题从根上消失。顺带与 monte_carlo 的 sampler 契约(决策 48 的 size=n)统一:
实测给 monte_carlo 写的采样器可以直接丢给 random_walk。关键点:不要用 .ravel() 救场 —— 那会把转置的二维摊平,
歧义原样回来。代价(已写进 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。
2026-10-09 更新:② 已办结(用户补了 np.any(arr < 0),含负元素的行随机矩阵现在入口直接报错);
① ③ ④ ⑤ 仍待拍板(A / B / C 三个选项见"下一步")。
| 57 | markov_steady_state 的口径:4 条已办、3 条 + 2 个新口子仍待拍板(Codex 只报选项,未代改,决策 49) |
2026-10-09 用户第二轮办结:① 补 np.any(arr < 0)(含负元素的行随机矩阵现在入口即报错,原先会溢出成 nan
却报 success=True);② tol 拆成 TypeError(类型 + bool)与 ValueError(取值);
④ 选口径 B:converged=False → success=False;⑥ params 补 transition(arr.tolist(),可 JSON 化)。
仍待拍板:③ tol=np.nan(nan <= 0 为假,穿过校验白跑 §max_iter 次迭代);
⑤ max_iter 的类型错仍抛 ValueError(与决策 54 的"类型错 TypeError"不一致);
⑦ 两把尺子宽严不一——非负校验用精确 arr < 0,同一个函数里的行和校验却用宽容的 np.allclose,
实测 [[1.0, -1e-16], [0.5, 0.5]](行和都是 1)会被判非法,建议改成 < -1e-12 这类容差;
⑧ 未收敛时 success=False 但 data 仍是未收敛向量(文件内唯一"失败却带 data"的分支)—— 保持还是 data=None?
| 58 | mm1_metrics 的稳定性分界由 Codex 按决策 31 直接修(< → <=),另有 4 条口径待用户拍板 |
docstring 与既有测试写的都是 λ >= μ 不稳定,代码却写 service_rate < arrival_rate —— 于是 λ == μ
掉进 try、rho == 1.0 → ZeroDivisionError,被 except 吞成 success=False
(碰巧对,但 message 成了"计算失败:ZeroDivisionError - float division by zero")。两支的可观测输出完全相同
(success=False / metrics={} / data=None),改 <= 只换 message,故直接修 + 加回归测试。
待拍板:① λ = 0 现在被 ValueError 拒(数学上是合法退化解:空系统 L = W = 0);
② np.int64 / np.float32 全被 TypeError 拒(文件内 random_walk 的 start 收 np.number、
整数参数收 np.integer,§4.7 有共识);③ data=metrics —— 同一个 dict 对象(r.data is r.metrics),
且成功分支给 dict、失败分支给 None,与文件内"data 是数组"的习惯不同;
④ 失败分支的 metrics={} 会丢掉 rho 等已算出的量(可选保留)。
下一步
⭐ 当前进度与待办(2026-10-09 第七轮)
本轮:你改了 markov_simulate / markov_steady_state 的入口,并写完 mm1_metrics —— 三处算法本体都正确。
Codex 只做工程侧:mm1_metrics 修了一处边界 bug(< → <=,见决策 58)、ruff 6 条、4 处文案参数名抄错、
docstring 补 Raises、+6 条测试 + 摘掉 2 条 skip。
三连:pytest 202 passed / 28 skipped、ruff 0、pyright 0。solve 层 L1 清单已全部落地。
接下来三件事
- 三个函数的口径待你拍板:
markov_simulate4 条(决策 56)、markov_steady_state3+2 条(决策 57)、mm1_metrics4 条(决策 58)—— 全在下面三节。 - L0 最后一项
save_figure(A 档 = 工程,viz/export.py)—— Codex 直接实现,你只读。 - 之后就往外层走:L1 只剩
decision/models/stats的几条。
⏳ markov_simulate 待你拍板(4 条)
① initial 的语义(最大的一条,三个选项)
现在标量分支按路径数铺满、数组分支按状态数校验长度,两条口径互相矛盾(详见决策 56)。
- A:只收标量状态,另加
initial_dist参数显式传"初始分布"(分布再抽一次,语义最干净,签名变长) - B:只收长度
n_paths的数组(标量走np.full铺满),data首列就是给定的初始状态序列(与paths[:, 0]赋值天然一致) - C:沿用现状,但把类型收成
int | np.integer+ 先判bool,把paths[:, 0] = ...挪进try,docstring 写清"数组按路径数"
② 转移矩阵没查非负 → 已办(2026-10-09):入口加了 np.any(arr < 0) → ValueError,
测试里钉了"行和是 1 但含负元素"这条。余下的一小问:这条校验是精确判负,见决策 57 的 ⑦(容差)。
③ paths[:, 0] 赋值在 try 之外:长度不匹配时裸抛 ValueError,与"出错统一 success=False"(决策 48)不一致。
④ 状态越界没在入口拦:initial=5(s=2)第一轮才 IndexError,被吞成 success=False;
建议入口加 0 <= initial < s。
⑤ std_terminal 对分类状态的意义:状态编号是标称量,均值和标准差只对"有序状态"(如排队人数)成立 ——
要不要把 metrics 换成状态频率(terminal_freq)?你定。
⏳ markov_steady_state 待你拍板(3 条 + 2 个新口子)
① 转移矩阵没查非负→ 已办(2026-10-09)②→ 已办:tol没查booltol现在拆成TypeError(类型 /bool)与ValueError(取值)④→ 已办:选了口径 B,converged=False仍success=Truesuccess=converged⑥→ 已办:补了params没记转移矩阵"transition": arr.tolist()
③ tol 没查 NaN:np.nan <= 0 为假 → 穿过校验 → 白跑满 10000 次迭代才返回(结果本身对,纯浪费)。
建议与 max_iter 一起走"类型 → 有限性 → 取值"三件套(易错点 28)。
⑤ max_iter 的类型错仍抛 ValueError(max_iter=100.0 / max_iter=True),
与文件内其它函数(决策 54:类型错 TypeError、取值错 ValueError)不一致 —— 要不要对齐?
⑦【新】非负校验的两把尺子:arr < 0 是精确判断,而同一个函数里的行和校验用的是宽容的 np.allclose。
实测 [[1.0, -1e-16], [0.5, 0.5]](行和都是 1.0,只是浮点归一化的残渣)会被判"含负元素"而报错。
建模时转移矩阵常常是从数据归一化出来的,这种 -1e-17 级残渣很常见 —— 建议改成 np.any(arr < -1e-12)
(或 arr.min() < -1e-12)这样的容差写法。markov_simulate 的同名校验也适用。
⑧【新】未收敛时 success=False 但 data 仍是未收敛向量:这是文件里唯一"失败却带 data"的分支
(其它失败分支一律 data=None)。调用方若只看 data is not None 就会拿到没收敛的结果 —— 保持(B)还是改 data=None(C)?
⏳ mm1_metrics 待你拍板(4 条)
① λ = 0 被拒:现在 arrival_rate <= 0 → ValueError。数学上 λ = 0 是合法退化解
(没有顾客到达,ρ = L = Lq = W = Wq = 0)—— 要收还是继续拒?
② numpy 标量全被拒:np.int64(2) / np.float32(2.0) → TypeError。
文件内其它函数已按 §4.7 放宽(random_walk 的 start 收 np.number、计数参数收 np.integer),
这里还是 (int, float) —— 要不要对齐?(np.float64 本来就是 float 的子类,不受影响)
③ data=metrics:data 和 metrics 是同一个 dict 对象(实测 r.data is r.metrics 为 True),
而且成功分支给 dict、失败分支给 None,与文件内"data 是样本数组"的习惯不同。选项:A 保持;
B data=None(闭式解没有样本数组);C 只留 metrics、data 给一个 5 元素数组。
④ 失败分支丢掉了已算出的量:λ >= μ 时 metrics={},但 ρ = λ/μ 其实算得出来
(ρ >= 1 本身就是"不稳定"的量化说明,论文里常要写)—— 要不要在失败分支里保留 rho?
你下一个要写的:solve 这层已经齐了(mm1_metrics 落地后 L1 清单走完)。
建议下一步挑 decision / models 里你感兴趣的 L1 函数开刀(E 节有清单),
或者先把上面三节的口径一次性拍完、Codex 批量收口 —— 两条路都行,你定。
✅ random_walk 的 4 条待办(2026-10-05 已办结)
① 非整数→ 已拆成两条:类型错n_steps抛ValueErrorTypeError、取值错(<= 0)ValueError,n_paths同样处理。②→ 已加steps没有形状校验steps.shape != (n_paths, n_steps)判失败(实测转置采样器现在能拦住)。③ NaN 不判失败→ 已加np.isnan(steps).any(),与monte_carlo的 NaN 口径(决策 48)拉齐。④→ 已补params少random_state/metrics抄了一遍random_state;metrics换成mean_terminal/std_terminal(终点位置的均值与标准差,ddof=0)—— 这才是"游走"该看的指标,测试里还顺手验了std ≈ √t。
✅ random_walk 的口径(2026-10-05 全部办结)
①→ 已恢复;文案的"必须是整数"改成"必须是数值"(这条校验本来就收 float), 类型名统一start的入口类型校验被删{type(start).__name__}。② 签名注解比行为窄→ 已改成int | np.integer,np.int64的测试补回来。③→ 用户选方案 B(决策 55):n_paths == n_steps的方阵歧义size=n_paths * n_steps(整数)、 要求返回一维、内部reshape(n_paths, n_steps);二维返回一律判失败。实测转置二维 / 方阵二维 / 长度不符全部拦下, 且monte_carlo风格的采样器可直接复用。
✅ monte_carlo_probability 的 6 条待办(2026-10-05 全部办结)
①→ 改成p <= 1e-3判success=Falsewarning_message,仍返回区间(实测 p=0 →(0.0, 0.00383))。② Wald 在 p 靠近 1 时退化→ 换 Wilson(p=1、n=1000:(1.0, 1.0)→(0.99617, 1.0))。③→ 已加。call_params没记confidence④→ 已加event/sampler不可调用被吞成success=Falsecallable校验,现在抛TypeError。⑤→metrics混np.float64float()统一(实测 4 个 key 全是float)。⑥→ 已改n: intint | np.integer。
还剩 1 条 → 已办(本轮)
→ 已改event: Callable[..., bool]的注解与行为不符Callable[..., Any],与sampler/simulator对齐。 pyright 复测:向量化事件函数不再被拒(原注解会报reportArgumentType)。决策 50 的遗留清零。
✅ monte_carlo_integral 的 3 条待办(2026-10-05 已办结)
①→ 已改成result.message被无条件覆盖if result.success:/else:两个分支; 失败时是"蒙特卡洛多重积分失败:维度 1,体积 1.0000,模拟 100 次。蒙特卡洛模拟失败:TypeError - ...",success与message不再打架。②→ 入口加了bounds形状没校验isinstance(bounds, (list, tuple))与ndim == 2 and shape[1] == 2两道;bounds=[0, 1]现在报"bounds 必须形如 [(lo, hi), ...],当前形状为 (2,),请检查是否漏了括号"。③→ 已改n: intint | np.integer。
✅ monte_carlo_integral 的 2 条新口径(2026-10-05 已办 1 条、剩 1 条)
①→ 已加bounds把 ndarray 拒了isinstance(bounds, np.ndarray)分支,np.array([[0.0, 1.0]])现在能收。②→ 已把if/else里那三行重复name/params["bounds"]/params["dimension"]提到if之前,只剩message分两支。
还剩 1 条(你的活)
- 第一道
isinstance分支现在是冗余的:np.asarray本来就同时收 list / tuple / ndarray, 而[]、"abc"、标量这些都会被下面那句形状校验抓住(报"当前形状为 (0,)"等)。 整段可以塌成两行:bounds_arr = np.asarray(bounds)+ 形状校验 —— 顺带bounds.copy()也是多余的一次拷贝。
⏳ monte_carlo 只剩 1 个口径要你确认
ci_low / ci_high 是不是就按"结果的分位数"定?
现状 np.percentile(data, [2.5, 97.5])。实测 n=2e5、E[X²]=1 给出 [0.001, 5.0455] ——
这是模拟结果的分散度(预测区间),与 p05 / p95 同类,只是分位点不同。
而"均值估计的置信区间"(MC 的统计误差)是 mean ± 1.96·std/√n = [0.9962, 1.0086],
两者差两个数量级,论文话术也完全不同:
- A(现状):写"95% 的模拟结果落在 [a, b]"(描述风险分布)
- B:改成均值 CI → 写"估计值 1.0024 ± 0.0043(95% CI)"(证明 n 够大)
- C:两者都要 → 指标要改名(如
p025/p975+mean_ci_low/mean_ci_high)
注意:monte_carlo_probability 里的 ci_* 已经是估计量的 CI(Wilson 区间,决策 50),所以这两个函数的 ci_*
目前含义不同——这正是要给它们定口径的原因之一。
✅ 已拍板(决策 48)
- 批量大小由实现传
size=n;sampler必须能接住size,**kwargs里不要再传size。 - 结果含 NaN →
success=False(不落 metrics)。 std用ddof=0。- 出错仍统一返回
success=False(try/except Exception保留)。
⏳ 另外两个小口径(你说了算)
random_state的注解:经验文档第 3 条写的标准是int | np.integer | np.random.Generator | None, 但stats/bootstrap.py、solve/simulate.py、stats/inference.py现在都写int | None(只有core/random.py是全的)—— 要不要全项目对齐?(运行期get_rng收np.int64,所以是注解与行为不一致,不是功能 bug)std的ddof:monte_carlo用ddof=0,describe那条线用ddof=1。要不要统一?
其余待办
-
L0 的最后一项是
save_figure(A 档 = 工程,viz/export.py)—— 由 Codex 直接实现,用户只读。 -
L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。
-
已拍板的旧口径(留档):
log_transform的offset维持现状(决策 35);describe/zscore默认丢掉非数值列(决策 37);变异系数用σ / |μ|(决策 38); 分工见决策 39;correlation_matrix走方案 A(决策 41)。 -
仍未答的一个小问题:
correlation_matrix现在只收DataFrame, 而describe/zscore/transform都收 ndarray —— 要不要也放开?(你说了算) -
t_test有 4 个口径等你拍板(Codex 一律没动,按决策 39):- ① 单样本的结论文案说错了。
b=None时test_name已经是"单样本 t 检验", 但conclusion仍写"两组数据存在统计学上的显著差异" —— 单样本根本没有第二组。 建议按分支拼文案(单样本说"该样本均值与value有显著差异")。 - ②
paired=True时 NaN 是按各自的位置丢的,会把不同行配成一对(静默错位,不报错)。 两个方向:配对前先成对删缺失,或直接拒绝含 NaN 的配对输入。你定。 - ③
value只在b is None时生效,同时传b和value会被静默忽略 —— 要不要改成报错? - ④
t_test(DataFrame)会把所有数值列摊平成一个样本(已写进Notes)。 多列不同量纲(gdp+pop)会被混在一起,而且没法拿"一张表里两列"当两组比。 要不要改成:多数值列的 DataFrame 直接ValueError、强制先自己取列?
- ① 单样本的结论文案说错了。
-
as_array落地后留下的 3 个小口径(Codex 没代选,按决策 39):int/float标量还在白名单里(为兼容normalize/standardize/log_transform三个 docstring 写的"数值型标量")。但标量进来是 0 维数组,axis=0的算法照样炸 —— 要不要把标量摘掉、同时改三处 docstring?- 内容转不成 float 时统一抛
TypeError(决策 45)——normalize(含文本列的表)原来抛ValueError。 要不要改回ValueError("取值问题")? - 现在
kind == "frame"时你要自己再写一遍x.select_dtypes(include=[np.number]).columns取列名。 嫌烦的话可以让as_array顺带把选中的列返回(改成三元组),一句话的事。
-
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]method="bca"→ docstring 承诺过、实测NotImplementedError(补实现 ≈25 行,还是从 docstring 删掉?)bootstrap_statistic仍是NotImplementedError,而bootstrap_ci把那段重采样循环内联了 —— 建议抽出去让bootstrap_ci调它(易错点 16 的变体:同一段算法写了两遍,只实现了一遍)