chore: 初始化 MCM/ICM Toolkit 仓库骨架

审查并精简 ChatGPT 的建库建议后落地的第一版骨架,只定接口不写实现。

- 建议审查:14 个包 → 7 个;约 250 个函数 → 约 40 个;5 个 Result 类 → 1 个
  (docs/01-suggestion-review.md)
- docs/:架构、路线图(含永久不做清单)、环境、Git 工作流、函数卡片模板
- src/mcm_toolkit/:core / data / stats / models / solve / decision / viz
  共 23 个模块,均为签名 + docstring + NotImplementedError
- tests/:9 个文件,其中 test_api_surface.py 已通过(20 passed)
- examples/ problems/ notebooks/ figures/ tables/ 骨架
- pyproject.toml(核心依赖 4 个 + 6 组 extras)、environment.yml
- scripts/:setup-venv.ps1(ASCII + LF)、setup-conda.ps1、doctor.ps1
- 环境:.venv(system-site-packages)与 conda 环境 mcm-toolkit 均已建好,
  数值栈版本与 base 完全对齐(numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6,MKL 后端)
This commit is contained in:
guzhujushi committed 2026-10-01 17:43:48 +08:00
commit 5319589dfd
76 files changed
+6812

No files matched your search

+207
View File
@@ -0,0 +1,207 @@
# ChatGPT 建库建议 · 审查与精简
> 被审查对象:仓库根目录 `chatGPT_Suggestion.md`(33 节,约 2.4 万字)
> 审查日期:2026-10-01
---
## 一、总裁决
**方向对,量级错。**
**方向对的地方(保留)**:把比赛的即用代码沉淀成"竞赛层薄封装 + 高频算法自己实现 + 统一结果接口",而不是攒一堆题解。这个定位是全文最有价值的部分,也是这个仓库存在的理由。
**量级错的地方(砍)**:原文给出 14 个子包、约 250 个函数、每个函数强制 10 项文档。这是一个人 **6~12 个月**的个人项目体量,不是支撑 **4 天**比赛的体量。按原文执行,最可能的结局是:前两周写完 `core` 和 `data`,然后在 `optimization/` 的 6 个文件面前失去动力,仓库停在半成品——这比"只有一个 20 个函数的 `mcm_toolkit.py`"更糟,因为半成品既不能用于比赛,也不好看。
**精简后的目标**:
| 维度 | ChatGPT 版 | 精简版 |
|---|---|---|
| 子包 | 14 | **7** |
| 函数(v0.1) | ~250 | **~40**(其中第一周只做 12 个) |
| Result 类 | 5 | **1** |
| 单函数文档要求 | 10 项,全体强制 | **2 档**,只有 ~15 个"招牌算法"做全卡片 |
| 预计完成周期 | —— | **4~6 周**出可用 v0.1 |
---
## 二、必须保留的(原文的干货)
| # | 原文主张 | 为什么留 |
|---|---|---|
| 1 | 「不要重新造轮子。`mcm_toolkit` 做竞赛层封装,而不是替代 SciPy/sklearn/NetworkX」 | 全文最重要的一句。它同时是设计原则和**防膨胀闸门** |
| 2 | 统一结果对象(`result.params / metrics / success`) | 真实痛点。比赛时最烦的就是每个函数返回不一样的东西 |
| 3 | 依赖分层:核心 4 个包 + `[project.optional-dependencies]` | 符合轻量化,且让 CI 和赛场安装都能可控 |
| 4 | 顶层 `__init__` 不 `import *`,只导出子包 + 少量高频快捷函数 | 保留 |
| 5 | 「`__all__` 是**公开 API 清单**,不是导出按钮」 | 保留,这条纪律要从第一版养成 |
| 6 | `problems/` 沉淀历年真题、`notebooks/` 做探索 | "每做一题反向沉淀一个通用函数"是这套东西能长大的唯一机制 |
| 7 | `save_figure(fig, path, dpi=300, formats=("png","pdf"))` | 美赛送稿硬需求,原文提了但严重低估——见第四节 |
| 8 | 参考库定位:SALib / NetworkX / statsmodels / pymoo / SymPy / sklearn | 六条定位全部准确,可直接当"别自己写"的白名单 |
| 9 | A / B / C 三级(自己写 / 自己封装 / 直接调用)的分类框架 | 框架留,**边界要下调**(见第三节第 10 条) |
---
## 三、要砍的
### 3.1 结构层:14 包 → 7 包
| 原文的包 | 处置 | 理由 |
|---|---|---|
| `core/` | **留**(4 → 3 文件) | 基础设施,必须有 |
| `data/` | **留**(4 文件) | 最高频 |
| `statistics/` | **留**,改名 `stats/`(5 → 3 文件) | S 级模块 |
| `regression/` + `ml/` | **合并为 `models/`** | 原文这两块 `fit_linear_regression` 与 `fit_linear` 明显重复,一套 `fit/predict/score` 协议就够了 |
| `optimization/` + `simulation/` + `sensitivity/` | **合并为 `solve/`** | 这三者是同一件事的三种形态:"把模型反复算"。分开建三个包,共享的 `Result`、`rng`、并行约定要在三处重复 |
| `dynamics/` | **降级**为 `solve/dynamics.py`,进 v0.2 | 原文自己承认"不一定主攻 MCM A"。MCM A 一年一题,且 SymPy+SciPy 封装只需 4 个函数,不值得一个独立包 |
| `network/` | **降级**为 `solve/graph.py`,进 v0.2 | 只有 ICM D 用;NetworkX 已全部提供。原文列了 5 个子模块 20+ 函数,其中 `degree_centrality` 之类就是一行转发 |
| `decision/` | **留但砍半**:8 → 4 文件 | AHP / TOPSIS / 熵权 / 灰色是中文赛高频,值得留;`dea`、`fuzzy` 见下 |
| `visualization/` | **改 `viz/`**,13 个 `plot_*` → 1 个 `plot(kind=...)` + 3 个专用图 | 见 3.3 |
| `utils/metrics.py` | **取消**,指标并入 `models/metrics.py` | 原文第 26 节又建议单列 `analysis/` 放同样的函数,三处重复 |
| 第 26 节的 `analysis/` | **取消** | 与 `utils/metrics.py` 功能完全重叠 |
| `docs/api/` `docs/tutorials/` | 保留占位,**不写内容** | 自动 API 文档用 mkdocs/sphinx 生成,手写必然过期 |
### 3.2 设计层
| 砍什么 | 理由 | 替代 |
|---|---|---|
| 5 个 Result 类 | 第一版没有任何一个函数需要区分这五种;过早分类会导致大量样板代码 | **1 个 `Result`**:`name / params / metrics / data / success / message`,后续真需要时再派生 |
| 每个函数 10 项文档(数学定义/适用场景/参数/实现/示例/测试/复杂度/数值稳定性/常见错误/MCM 案例) | 对一个一行转发的封装函数写 10 项文档,写作成本是实现的 20 倍。**这是最可能导致项目停滞的一条** | 两档制,见第七节 |
| 「`__init__.py` 里再导出少量高频函数」的具体选择 | 原文选的是 `solve_ode`——但 ODE 已被降级 | 改成 `monte_carlo / bootstrap_ci / minimize / parameter_sweep / topsis / compare_models` |
### 3.3 函数层:要删的清单
> 判断标准只有两条:**美赛近 10 年题面出现过吗?** / **成熟库是不是一行就够?** 两条都不满足就删。
| 删除项 | 理由 | 替代 |
|---|---|---|
| `maximize()` | 就是 `minimize` 取负 | `minimize` 加 `sense="max"` 参数 |
| `kendall_correlation` / `partial_correlation` | 美赛几乎不用;`df.corr(method="kendall")` 一行 | pandas |
| `modified_zscore_outliers` / `zscore_outliers` | IQR 和 z-score 两种覆盖 95% 场景 | 只留 `detect_outliers(method="iqr"|"zscore")` |
| `distribution_pdf/cdf/ppf` 三个独立函数 | 纯转发 scipy | `fit_distribution` 返回的对象上挂 `.pdf/.cdf/.ppf` |
| `sample_without_replacement` / `sample_with_replacement` | `rng.choice(..., replace=)` 一行 | numpy |
| `brownian_motion` / `geometric_brownian_motion` | niche,只有金融题偶尔用 | 用到再写;`random_walk` 已覆盖 |
| `mmc_metrics` / `simulate_queue` | 排队论在美赛出现频率极低,且 `simulate_queue` 是个完整的离散事件仿真引擎,成本极高 | 只留 `mm1_metrics`(闭式解,10 行) |
| `crowding_distance` / `hypervolume` | NSGA-II 内部件,属于原文自己划的 C 类 | pymoo |
| `dea_efficiency` | 低频;且要解一堆 LP,实现成本不低 | 真要用,现场用 `scipy.optimize.linprog` 拼 |
| `fuzzy_comprehensive_evaluation` | 低频,且"隶属度函数"没有通用形式,做不了通用封装 | 删 |
| `value_iteration` + `policy_iteration` + `dynamic_programming` 三个 | 三者是同一族的三种写法,第一版同时做属于过度设计 | 只留 `dynamic_programming`(通用)+ `knapsack_dp`(模板) |
| `weighted_moving_average` / `holt_trend` / `seasonal_decompose` | statsmodels 一行。原文自己也在第 7 节说"做薄封装" | 只留 `exponential_smoothing` 一个包装 |
| `grid_sweep` | `parameter_sweep` 的特例 | 合入 `parameter_sweep` |
| 13 个 `plot_*` | 每个都是 `ax` 上加两行;拆成 13 个函数后,改一次全局样式要改 13 处 | `plot(x, y, kind="line"|"scatter"|"hist"|"box"|"heatmap")` + `plot_correlation_matrix` / `plot_residuals` / `plot_convergence` 三个有真实逻辑的 |
| `normalize` 与 `minmax_scale` 并存 | 是同一个东西的两个名字 | 只留 `normalize` |
| `standardize` 与 `zscore` 并存(一个在 data、一个在 stats) | 同上 | 只留 `data.standardize` |
| `higher-level`:`sample_distribution`、`distribution_pdf` 系列 | 转发 | 删 |
**净效果:约 250 → 约 40 个函数。**
### 3.4 下调 A/B/C 的边界
原文的 A 类("强烈建议自己实现")里有 `dynamic programming`、`genetic algorithm`、`sensitivity analysis` 等。**用于学习时**这个清单没问题;但**比赛现场**要反过来:
- 比赛时:**能用库就用库**(SALib 的 `sobol_analyze` 一定比自己写的对)。
- 平时:自己实现,目的是"读得懂、改得动、能解释给队友"。
- 所以自己实现的代码要单独标 `_from_scratch` 或放在 `mcm_toolkit/algo/`,默认走库版本。
原文把这两个场景混在一起了。
---
## 四、ChatGPT 漏掉的(真正决定比赛成败的)
原文 33 节几乎全是"代码结构",但**美赛是论文比赛,不是代码比赛**。以下六条原文完全没提或严重低估:
1. **论文产出链路(最严重的缺失)**
工具箱的最终消费者是**论文**。原文只给了一个 `save_figure`。至少要补:
- `save_table(df, path, fmt=("csv","md","latex"))` —— 结果表直接出 LaTeX,粘贴进论文
- 全局图样式 `mcm_style()`:字号 ≥ 9pt、线宽 ≥ 1.5、`dpi=300`、色盲友好调色板、双栏宽度
- 图注(`fig.caption`)与"图号—正文引用"的约定
2. **量纲与方向**
`normalize` 没区分"无量纲化"和"标准化",更没提**效益型 / 成本型指标方向**。评价类题目(TOPSIS/熵权)翻车基本都翻在这里。
3. **时间预算**
4 天比赛,函数的价值 = 正确性 ÷ 上手时间。原文没有一条关于"够快"的约定:MC 必须向量化、`n_resamples` 默认值要能在一分钟内跑完、不要引入编译开销。
4. **可复现性**
提了 `get_rng`,但没提"提交要能复现"。约定:所有带随机的公开函数第一个参数是 `random_state`,论文附录必须写 seed。
5. **测试测什么**
说了 pytest,没说测什么。应测 `已知解析解`:MC 积分 vs 解析积分、正态 CI vs 公式值、`bootstrap_ci` 大样本下收敛到 CLT 值。**不要测实现细节**。
6. **离线约束**
原文的 extras 设计假设比赛机器能联网。现场装不上包是真实风险,必须有 `environment.yml` 锁版本 + 一份"最小可用依赖"清单。
另外两条工程问题:
7. **原文第 32 节把 `problems/` `notebooks/` 和 `src/` 混在一个仓库**——这是对的,但要注意 `problems/` 会带来大量数据文件,`.gitignore` 要提前写好(见仓库根目录的 `.gitignore`)。
8. **没有"不做"清单**。原文用"建议不要",但真正能防膨胀的是写死的"本仓库**永远不**实现 X"。已补进 `docs/03-roadmap.md`。
---
## 五、精简后的结构
```text
MCM&ICM_Toolkit/
├── src/mcm_toolkit/
│ ├── core/ 基础设施:Result / rng / 校验 ← 第一个做
│ ├── data/ 读、洗、变换、采样 ← 最高频
│ ├── stats/ 描述、推断、bootstrap ← S 级
│ ├── models/ 回归 + 时序 + ML 薄封装(统一 fit/predict/score)
│ ├── solve/ 优化、模拟、敏感性、图、动力学 ← 竞赛主力
│ ├── decision/ 熵权 / AHP / TOPSIS / 灰色
│ └── viz/ 统一绘图 + 图表导出
├── tests/ tests/test_*.py(按包一一对应)
├── examples/ 每个模块一个能跑的最小例子
├── problems/ 历年真题(2025_C/ ...)
├── notebooks/ 探索用
├── docs/ 本目录
├── scripts/ setup-conda.ps1 / setup-venv.ps1
├── pyproject.toml
├── environment.yml
└── STATUS.md 进度
```
包结构详情与每个包的边界见 `02-architecture.md`。
---
## 六、功能清单分级
见 `03-roadmap.md`。摘要:
- **L0(第一周,12 个)**:`Result` `get_rng` `load_table` `clean_missing` `detect_outliers` `normalize` `standardize` `describe` `correlation_matrix` `bootstrap_ci` `monte_carlo` `save_figure`
- **L1(v0.1,共约 40 个)**:L0 + 回归/时序/优化/敏感性/评价/绘图
- **L2(v0.2)**:`dynamics`、`graph`
- **永久不做**:见 `03-roadmap.md` 末节
---
## 七、开发纪律:文档从 10 项降到 2 档
原文要求**每个**函数都写 10 项。改成两档:
**S 档(招牌算法,约 15 个)** —— 用 `docs/dev/function-card-template.md` 的完整卡片:
数学定义 / 适用场景 / 参数 / 复杂度 / 数值稳定性 / 常见错误 / 最小示例 / 单测 / 赛题用法。
覆盖:`bootstrap_ci` `monte_carlo` `simulated_annealing` `dynamic_programming` `parameter_sweep` `sobol_analyze` `topsis` `entropy_weights` `ahp_weights` `gm11` `detect_outliers` `local_sensitivity` 等。
**A 档(其余,薄封装)** —— 只要求:
1. docstring 里**一句话数学含义 + 一句话什么时候用**;
2. 一条 `pytest`(对齐库结果或已知值)。
不单独写文档。
> 判断标准:**我会不会去改它的内部实现?** 会 → S 档;只当转发器用 → A 档。
---
## 八、执行顺序(时间盒)
| 周 | 目标 | 交付 |
|---|---|---|
| 1 | `core` + `data` + `stats` 骨架;S 档先写 `bootstrap_ci` `monte_carlo` `normalize` `detect_outliers` | L0 全部可跑通 |
| 2–3 | `models` + `solve/optimize` + `solve/simulate` | `compare_models` 能跑 |
| 4 | `solve/sensitivity` + `decision` + `viz` | 一套端到端 example |
| 5–6 | 拿一道真题(如 2025 C)从头走一遍,反向补函数 | `problems/2025_C/` 完成 |
| 之后 | 每做一题 → 沉淀 1~3 个函数 + 对应 test | 仓库自然长大 |
**这个顺序的关键是"先能跑通一条完整链路,再补宽度"**,与原文"先把 14 个包建齐"相反。
+178
View File
@@ -0,0 +1,178 @@
# 架构设计
> 上游依据:`01-suggestion-review.md`。本文件定义"代码往哪写、边界在哪、接口长什么样"。
---
## 一、设计原则
1. **薄封装优先。** 能用 SciPy / sklearn / statsmodels / NetworkX / SALib 一行解决的,就只做一行转发 + 参数校验 + `Result` 包装。本仓库**不**替代这些库。
2. **自己实现是有目的的。** 只有"库里没有、或者自己写才学得会"的算法才手写(bootstrap、Monte Carlo、DP、SA、熵权、TOPSIS、灰色……)。手写实现要能被读懂、能被改。
3. **统一出口。** 凡是"拟合 / 求解 / 模拟"类函数,一律返回 `Result`,字段固定。
4. **可复现。** 所有含随机的公开函数都接受 `random_state`,默认 `None`(用全局 RNG)。
5. **默认值面向比赛。** 默认参数应当在 4 天赛程里"直接能跑完",不用调。
---
## 二、包结构(7 个包)
```text
src/mcm_toolkit/
├── __init__.py 只导出 6 个高频快捷函数 + 7 个子包
├── _version.py
│
├── core/ ① 基础设施 —— 所有包的地基
│ ├── result.py Result
│ ├── random.py set_seed / get_rng
│ └── validate.py check_array / check_same_length / check_probability / check_square
│
├── data/ ② 数据进出 —— 最高频
│ ├── io.py load_table / save_table
│ ├── clean.py clean_missing / impute_missing / detect_outliers / winsorize
│ ├── transform.py normalize / standardize / log_transform / one_hot_encode
│ └── sample.py train_test_split / kfold_split / bootstrap_sample
│
├── stats/ ③ 统计 —— S 级模块
│ ├── descriptive.py describe / zscore / coefficient_of_variation
│ ├── inference.py confidence_interval / t_test / anova / chi_square_test
│ │ fit_distribution / goodness_of_fit
│ └── bootstrap.py bootstrap_statistic / bootstrap_ci ★ 自己实现
│
├── models/ ④ 建模 —— 回归 / 时序 / ML,统一协议
│ ├── base.py Model 协议:fit / predict / score
│ ├── metrics.py rmse / mae / mape / r2_score / adjusted_r2 / compare_models
│ ├── regression.py fit_linear / fit_polynomial / fit_nonlinear / predict
│ ├── timeseries.py exponential_smoothing / fit_arima / forecast
│ └── ml.py fit_forest / fit_logistic / kmeans / pca / cross_validate
│
├── solve/ ⑤ 求解 —— 竞赛主力(把模型反复算)
│ ├── optimize.py minimize / linear_program / integer_program
│ │ dynamic_programming / knapsack_dp ★ 自己实现
│ │ simulated_annealing ★ 自己实现
│ │ pareto_front
│ ├── simulate.py monte_carlo / monte_carlo_probability / monte_carlo_integral
│ │ random_walk / markov_simulate / markov_steady_state
│ │ mm1_metrics
│ ├── sensitivity.py local_sensitivity / one_at_a_time / parameter_sweep
│ │ robustness_test / sobol_analyze / morris_analyze
│ ├── graph.py 【v0.2】build_graph / shortest_path / centrality / pagerank / max_flow
│ └── dynamics.py 【v0.2】solve_ode / solve_system / find_equilibrium / local_stability
│
├── decision/ ⑥ 评价决策 —— 中文赛特色
│ ├── weighting.py entropy_weights / critic_weights / equal_weights
│ ├── ahp.py ahp_weights / ahp_consistency_ratio
│ ├── topsis.py topsis
│ └── grey.py grey_relational_analysis / gm11_fit_predict
│
└── viz/ ⑦ 出图出表 —— 论文产出的最后一公里
├── style.py mcm_style
├── plot.py plot / plot_correlation_matrix / plot_residuals / plot_convergence
└── export.py save_figure / save_table
```
★ = 自己实现(S 档,配完整函数卡片)
---
## 三、与 ChatGPT 原方案的对照
| 原方案 | 现方案 | 变更理由 |
|---|---|---|
| 14 个包 | 7 个包 | 见 `01-suggestion-review.md` §3.1 |
| `regression/` + `ml/` | 合并 `models/` | 原方案里 `fit_linear` 与 `fit_linear_regression` 重复 |
| `optimization/` + `simulation/` + `sensitivity/` | 合并 `solve/` | 三者共享 Result / rng / 并行约定,分开会重复三遍 |
| `dynamics/` `network/` | 降级为 `solve/dynamics.py` `solve/graph.py`,进 v0.2 | 低频,不值得独立包 |
| `utils/metrics.py` + 单列 `analysis/` | 合并 `models/metrics.py` | 原方案三处重复放同一批指标 |
| 13 个 `plot_*` | `plot(kind=...)` + 3 个专用图 | 改全局样式不必改 13 处 |
| 5 个 Result 类 | 1 个 `Result` | 第一版没有一个函数需要区分五种结果 |
---
## 四、关键约定
### 4.1 `Result`(唯一结果对象)
```python
@dataclass
class Result:
name: str # 产生它的函数名
params: dict | None = None # 参数估计 / 决策变量取值
metrics: dict | None = None # 评估指标
data: Any = None # 主要载荷(预测值 / 轨迹 / 排序……)
success: bool = True
message: str = ""
```
约定:
- 任何"拟合 / 求解 / 模拟 / 评价"函数**只返回 `Result`**,不返回裸 tuple。
- `Result` 支持 `repr` 直接打印成"指标表",方便贴进论文草稿。
- 后续真需要区分类型时再派生,不提前建 5 个类。
### 4.2 随机数
```python
set_seed(seed) -> None # 全局种子
get_rng(random_state=None) -> np.random.Generator
```
- 所有含随机的函数签名统一含 `random_state=None`,内部第一行 `rng = get_rng(random_state)`。
- **禁止**直接用 `np.random.*` 旧接口,禁止裸用 `random` 模块。
- 论文附录必须写明 seed。
### 4.3 模型协议
```python
class Model(Protocol):
def fit(self, X, y, **kw) -> "Model": ...
def predict(self, X) -> np.ndarray: ...
def score(self, X, y) -> dict: ... # 返回指标 dict,不是单个 float
```
- 这样 `compare_models({"linear": m1, "rf": m2}, X, y)` 才能统一执行。
- `compare_models` 返回 `pd.DataFrame`(行=模型,列=指标),可直接 `save_table(..., fmt="latex")`。
### 4.4 指标方向与量纲(评价类必读)
- `normalize` 只做**无量纲化**(min-max / 向量归一),不改变指标方向。
- 方向(效益型 / 成本型)在 `decision` 层处理,用参数 `benefit_columns` 显式声明。
- **不允许**默认假设"越大越好"。
### 4.5 import 规则
- 顶层 `mcm_toolkit/__init__.py` 只导出子包 + 6 个快捷函数,**不 `import *`**。
- 子包之间允许单向依赖:`core ← data / stats / models / solve / decision / viz`;`viz` 可依赖其余全部;**禁止反向依赖**。
- `__all__` 是公开 API 清单,不写"内部工具"。
### 4.6 依赖分层
| 层 | 包 | 说明 |
|---|---|---|
| 核心(必装) | `numpy` `scipy` `pandas` `matplotlib` | `core` `data` `stats` `viz` 只需要这些 |
| `[ml]` | `scikit-learn` `statsmodels` | `models/` |
| `[graph]` | `networkx` | `solve/graph.py` |
| `[sensitivity]` | `SALib` | `sobol_analyze` / `morris_analyze` |
| `[symbolic]` | `sympy` | `solve/dynamics.py` |
| `[moo]` | `pymoo` | 真需要 NSGA-II 时 |
| `[excel]` | `openpyxl` | Excel 读写 |
| `[dev]` | `pytest` `ruff` `pyright` | 开发 |
**可选依赖缺失时必须给出可读报错**,例如 `sobol_analyze` 在没装 SALib 时提示 `pip install "mcm-toolkit[sensitivity]"`,而不是 `ModuleNotFoundError` 裸抛。
---
## 五、测试策略
- 目录 `tests/` 与包一一对应(`tests/test_stats.py` ↔ `stats/`)。
- **测已知解析解,不测实现细节**:
| 函数 | 测法 |
|---|---|
| `monte_carlo_integral` | 对 `∫₀¹ x² dx = 1/3` 收敛,误差 < 1e-2(n=1e5) |
| `bootstrap_ci` | 正态样本大 n 下,百分位 CI ≈ 解析 CI(±5%) |
| `confidence_interval` | 与 `scipy.stats.norm.interval` 对齐 |
| `fit_polynomial` | 与 `np.polyfit` 系数对齐(`allclose`) |
| `detect_outliers` | 人工构造带 3 个离群点的数组,命中数 = 3 |
| `topsis` | 与手算的排序一致 |
- 随机相关测试一律固定 `random_state`。
- 每条断言都要能说清"错了说明什么"。
+112
View File
@@ -0,0 +1,112 @@
# 路线图与开发纪律
> 依据:`01-suggestion-review.md` §6–§8。
> 进度看根目录 `STATUS.md`,本文件只定义"做什么、做到什么程度、什么不做"。
---
## 一、功能清单分级
### L0 —— 第一周必做(12 个)
没有这 12 个,工具箱根本跑不起来;有了这 12 个,已经能应付大部分题目。
| 函数 | 所在模块 | 档位 |
|---|---|---|
| `Result` | `core/result.py` | A |
| `get_rng` / `set_seed` | `core/random.py` | A |
| `load_table` | `data/io.py` | A |
| `clean_missing` | `data/clean.py` | A |
| `detect_outliers` | `data/clean.py` | **S** |
| `normalize` | `data/transform.py` | A |
| `standardize` | `data/transform.py` | A |
| `describe` | `stats/descriptive.py` | A |
| `correlation_matrix` | `stats/inference.py` | A |
| `bootstrap_ci` | `stats/bootstrap.py` | **S** |
| `monte_carlo` | `solve/simulate.py` | **S** |
| `save_figure` | `viz/export.py` | A |
### L1 —— v0.1(共约 40 个)
在 L0 基础上补齐:
| 模块 | 函数 |
|---|---|
| `core` | `check_array` `check_same_length` `check_probability` `check_square_matrix` |
| `data` | `impute_missing` `winsorize` `log_transform` `one_hot_encode` `train_test_split` `kfold_split` `bootstrap_sample` |
| `stats` | `zscore` `coefficient_of_variation` `confidence_interval` `t_test` `anova` `chi_square_test` `fit_distribution` `goodness_of_fit` `bootstrap_statistic` |
| `models` | `rmse` `mae` `mape` `smape` `r2_score` `adjusted_r2` `compare_models` `fit_linear` `fit_polynomial` `fit_nonlinear` `predict` `exponential_smoothing` `fit_arima` `forecast` `fit_forest` `fit_logistic` `kmeans` `pca` `cross_validate` |
| `solve` | `minimize` `linear_program` `integer_program` `dynamic_programming` `knapsack_dp` `simulated_annealing` `pareto_front` `monte_carlo_probability` `monte_carlo_integral` `random_walk` `markov_simulate` `markov_steady_state` `mm1_metrics` `local_sensitivity` `one_at_a_time` `parameter_sweep` `robustness_test` `sobol_analyze` `morris_analyze` |
| `decision` | `equal_weights` `entropy_weights` `critic_weights` `ahp_weights` `ahp_consistency_ratio` `topsis` `grey_relational_analysis` `gm11_fit_predict` |
| `viz` | `mcm_style` `plot` `plot_correlation_matrix` `plot_residuals` `plot_convergence` `save_table` |
### L2 —— v0.2
- `solve/graph.py`:`build_graph` `shortest_path` `min_spanning_tree` `betweenness_centrality` `pagerank` `max_flow` `community_detection`
- `solve/dynamics.py`:`solve_ode` `solve_system` `find_equilibrium` `local_stability`
**触发条件**:在真题里真的用到了,或者要为 MCM A / ICM D 备赛时再写。
---
## 二、永久不做(防膨胀清单)
> 原文用"建议不要",这里写成**硬规则**。往这个清单里加东西,必须先改这份文档。
- 不重写 SciPy 的优化器、`scipy.stats` 的分布、`scipy.integrate` 的积分器
- 不重写 sklearn 的 estimator(`fit/predict` 之外一律不做)
- 不重写 statsmodels 的时间序列框架
- 不重写 NetworkX 的图算法
- 不重写 SALib 的 Sobol / Morris / FAST
- 不实现 NSGA-II / SPEA2 等完整多目标进化算法(用 pymoo)
- 不实现符号计算引擎(用 SymPy)
- 不实现完整离散事件仿真引擎(`simulate_queue` 那种)
- 不做深度学习 / PyTorch 相关任何东西
- 不做 GUI、不做 Web 界面、不做 CLI 之外的产品化
- 不做自动微分、不做 GPU 后端
- 不为"完整性"补函数——**没在真题里用过三次的函数不写**
---
## 三、开发纪律
### 3.1 文档两档制
**S 档**(招牌算法,自实现):完整函数卡片,模板见 `dev/function-card-template.md`。
覆盖:`detect_outliers` `bootstrap_ci` `monte_carlo` `monte_carlo_probability`
`monte_carlo_integral` `dynamic_programming` `knapsack_dp` `simulated_annealing`
`local_sensitivity` `sobol_analyze` `entropy_weights` `topsis` `ahp_weights`
`gm11_fit_predict`。约 15 个。
**A 档**(薄封装):docstring 里一句话数学含义 + 一句话什么时候用 + 一条 pytest。
判断标准:**我会不会去改它的内部实现?** 会 → S 档。
### 3.2 每写一个函数
1. 先想"这道题里它替代了哪段手写代码"——想不出来就先不写。
2. 先在 `tests/` 里写一条会失败的断言(测已知解析解,不测实现细节)。
3. 实现到测试通过。
4. 在 `examples/` 里加一行最小用法。
5. 更新 `STATUS.md`。
### 3.3 提交前
```powershell
.venv\Scripts\python.exe -m pytest -q
.venv\Scripts\python.exe -m ruff check src tests
```
---
## 四、时间盒
| 周 | 目标 | 完成标志 |
|---|---|---|
| 1 | `core` + `data` + `stats`;S 档先写 `bootstrap_ci` `monte_carlo` `detect_outliers` | L0 全部通过测试 |
| 2–3 | `models` + `solve/optimize` + `solve/simulate` | `compare_models` 出对比表 |
| 4 | `solve/sensitivity` + `decision` + `viz` | 一条端到端 example |
| 5–6 | 用一道真题走完整流程,反向补函数 | `problems/<年份>_<题号>/` 完成 |
| 之后 | 每做一题沉淀 1~3 个函数 + 对应 test | 仓库自然长大 |
**核心原则:先跑通一条完整链路,再补宽度。**
+238
View File
@@ -0,0 +1,238 @@
# 环境配置
> 记录本机(Windows / PowerShell)上 conda 与 venv 两套环境的**实际**配置方式、
> 实测数据、以及踩过的坑。相关脚本在 `scripts/`。
>
> 状态:**两套环境均已建好、版本已对齐、验证通过**(2026-10-01)。
---
## 一、结论速查
| | venv | conda env |
|---|---|---|
| 路径 | `D:\MCM&ICM_Toolkit\.venv` | `D:\anaconda\envs\mcm-toolkit` |
| 解释器 | Anaconda base,Python **3.13.9** | Python **3.13.15** |
| 占用 | 10.5 MB | 1.55 GB(+ 共用 pkgs 缓存) |
| 来源 | `--system-site-packages` 复用 base | conda 从 defaults 通道安装 |
| 用途 | 轻量开发 / 离线兜底 | **跑题主力环境** |
| 缺什么 | SALib、pymoo、pyright | 无缺口 |
**两套的数值计算栈已完全一致**(见 §5),只差 Python 补丁号。可以放心在两套之间切换。
---
## 二、venv(轻量、离线可用)
```powershell
D:\anaconda\python.exe -m venv --system-site-packages --prompt "mcm-toolkit" .venv
.venv\Scripts\python.exe -m pip install -e . --no-deps --no-build-isolation
```
一键:`./scripts/setup-venv.ps1`
### 为什么用 `--system-site-packages`
让 venv 直接复用 Anaconda base 里已有的包,**零下载**就有可用的科学栈,
而且只有 10.5 MB,可以安心放在项目目录里。
已验证可导入(版本随 base 走,2026-10-01 base 升级后为):
`text
numpy 2.4.6 scipy 1.18.1 pandas 3.0.6 matplotlib 3.11.2
scikit-learn 1.9.1 statsmodels 0.15.0 networkx 3.6.1 sympy 1.14.0
`
### `--no-deps --no-build-isolation` 是必须的
* `--no-deps`:依赖已由 base 提供,不必联网解析;
* `--no-build-isolation`:否则 pip 会新建隔离环境去下载 setuptools 构建后端。
---
## 三、conda 环境 `mcm-toolkit`(跑题主力)
```powershell
# 一键(推荐)
.\scripts\setup-conda.ps1
# 等价的手工命令
$env:CONDA_PLUGINS_AUTO_ACCEPT_TOS = 'yes' # 见 §4 坑 1
conda create -n mcm-toolkit -y python=3.13 ^
numpy scipy pandas matplotlib scikit-learn statsmodels ^
networkx sympy openpyxl pytest ruff pip
```
**实测:约 14.5 分钟(868 秒),下载约 3 GB,环境本体 1.55 GB。**
建完后补装 pip-only 的依赖并安装本项目:
```powershell
$py = 'D:\anaconda\envs\mcm-toolkit\python.exe'
& $py -m pip install SALib pymoo pyright
& $py -m pip install -e . --no-deps --no-build-isolation
```
这些 `setup-conda.ps1` 已全部自动完成。
### 为什么不推荐 `--clone base`
原以为 `conda create -n X --clone base` 是"本地复制、秒级完成、不用下载"。
**实测不是。** 在 conda 25.11 上它会重放 base 的完整包列表并逐个下载安装
(连 pandoc、m2-msys2-runtime、anaconda-navigator 这些都用不上的也照下),
跑 253 秒才下完约 2 GB 就已被中止,按比例算远慢于精准列表。
所以 `setup-conda.ps1` 的默认路径是**精准包列表**;`-CloneBase` 只在你确实想
要一份 base 的完整副本时才用。
### 断网怎么办
**提前建好环境**,并保留 `D:\anaconda\pkgs` 缓存(现在 8.4 GB)。
包已在缓存里时 `conda create --offline` 才能工作;否则会失败。
比赛现场不要指望临时建环境,走 venv(它 100% 离线可用)。
---
## 四、踩过的坑
**坑 1 —— Anaconda ToS 插件要联网。**
Anaconda 25.x 自带 `conda_anaconda_tos` 插件,任何 `create/install` 之前都会去
`repo.anaconda.com` 拉 ToS 元数据。断网时它不是报"没网",而是抛一长串 traceback。
```powershell
$env:CONDA_PLUGINS_AUTO_ACCEPT_TOS = 'yes' # 推荐:跳过联网,保留 libmamba 求解器
conda --no-plugins create ... # 彻底关插件(退化成经典求解器,慢)
```
**坑 2 —— `envs_dirs` / `pkgs_dirs` 必须可写。**
不可写时报 `NotWritableError` 或
`Error encountered while attempting to create cache directory`。排查:
```powershell
conda info --json | Select-String "envs_dirs","pkgs_dirs"
conda config --show envs_dirs pkgs_dirs
```
**坑 3 —— 被中止的 `conda create` 用 `conda env remove` 删不干净。**
本次中止后 `conda env remove -n mcm-toolkit -y` 跑完,`D:\anaconda\envs\mcm-toolkit`
**仍然存在**且 `conda env list` 里已看不到它。要手工删目录:
```powershell
Remove-Item -LiteralPath 'D:\anaconda\envs\mcm-toolkit' -Recurse -Force
```
**坑 4 —— 别把 conda env 建在项目目录里。**
`conda create -p ./.conda` 会把 GB 级目录放进仓库,脏 `.gitignore` 也容易误提交。
**坑 5 —— 版本漂移。**
本机 base 是 2025-10 装的,defaults 通道已是 2026-10 的版本,于是新建的 conda
环境拿到的是更新的版本(pandas 从 2.3.3 跳到 **3.0.6**,是 major 升级)。
见 §5,写代码时注意别依赖两套环境都有的行为。
---
## 五、两套环境的实际版本(已对齐)
**2026-10-01 已把 base 从 Anaconda 2025.12 升到 2026.07,两套环境版本已对齐。**
| 包 | venv(继承 base) | conda `mcm-toolkit` | |
|---|---|---|---|
| Python | 3.13.9 | 3.13.15 | 补丁号,可忽略 |
| numpy | 2.4.6 | 2.4.6 | ✅ 一致 |
| scipy | 1.18.1 | 1.18.1 | ✅ 一致(连 build 号都相同) |
| pandas | 3.0.6 | 3.0.6 | ✅ 一致 |
| matplotlib | 3.11.2 | 3.11.2 | ✅ 一致 |
| scikit-learn | 1.9.1 | 1.9.1 | ✅ 一致 |
| statsmodels | 0.15.0 | 0.15.0 | ✅ 一致 |
| networkx | 3.6.1 | 3.6.1 | ✅ 一致 |
| sympy | 1.14.0 | 1.14.0 | ✅ 一致 |
| pytest | 9.1.1 | 9.1.1 | ✅ 一致 |
| SALib / pymoo / pyright | 缺失 | 已装 | venv 本来就只做轻量开发 |
| BLAS 后端 | **MKL** | **MKL** | ✅ 一致 |
### base 升级记录
[FENCE]powershell
conda update -n base --all -y # 365 个包更新,下载 638 MB,耗时 1033 秒
conda install -n mcm-toolkit numpy=2.4.6 -y # 把项目环境对齐到 base 的 numpy
[FENCE]
* `conda` 本身从 25.11.1 升到 **26.7.3**。
* 升级前 `conda list -n base --revisions` 里已留有 revision 记录,可回滚:
`conda install -n base --revision <N>`。
* **踩过的坑**:base 想装 `numpy=2.5.3` 时求解器会顺手把 BLAS 从 **MKL 换成 OpenBLAS**
(`_anaconda_depends 2026.07 ..._mkl_1 → ..._openblas_1`、`blas 1.0-mkl → 1.0-openblas`)。
`_anaconda_depends` 把 base 的 numpy 钉在 2.4.6,硬升会牵动整套线性代数后端,
所以选择把项目环境的 numpy **降到 2.4.6** 来对齐,而不是动 base。
* 升级期间遇到 `repo.anaconda.com` 读超时(默认 9.15 秒太短),已把
`remote_read_timeout_secs` 调到 120、`remote_connect_timeout_secs` 调到 30、
`remote_max_retries` 调到 5。这几项写在 `~/.condarc` 里。
## 六、国内镜像(需要装新包时)
```powershell
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
conda config --set show_channel_urls yes
# pip(对 venv 和 conda 环境都生效)
python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
```
> 本机当前通过 `http://127.0.0.1:7993` 的 UniClash 代理直连,未配镜像。镜像只影响**下载**,
> 不影响坑 1 的 ToS 行为。
---
## 七、可选依赖
核心 4 个包(`numpy scipy pandas matplotlib`)两套环境都有。其余按需:
```powershell
python -m pip install -e ".[ml]" # sklearn statsmodels 已有
python -m pip install -e ".[graph]" # networkx 已有
python -m pip install -e ".[sensitivity]" # SALib conda 环境已有
python -m pip install -e ".[symbolic]" # sympy 已有
python -m pip install -e ".[moo]" # pymoo conda 环境已有
python -m pip install -e ".[excel]" # openpyxl 已有
python -m pip install -e ".[dev]" # pytest ruff pyright
```
缺依赖时对应函数应当给出可读的安装提示,而不是裸抛 `ImportError`
(写成约定,见 `docs/02-architecture.md` §4.6)。
---
## 八、脚本的编码约定(踩过的坑)
`scripts/` 下的 `.ps1` **必须是纯 ASCII + UTF-8 无 BOM + LF**。
原因是 Windows PowerShell 5.1 **在没有 BOM 时按 ANSI(本机 GBK)解析 `.ps1`**,
中文注释和中文消息会被拆成非法字节,报出来的却是一堆莫名其妙的
`Missing closing '}' in statement block` / `The string is missing the terminator`。
(实测:带中文的 `doctor.ps1` 直接语法错误;改成纯 ASCII 后立刻正常。)
两种可行方案,本项目选了第二种:
1. 保留中文 → 必须存成 **UTF-8 with BOM**(违反 AGENTS.md 的"无 BOM"约定);
2. **纯 ASCII,注释与输出用英文** → 符合 AGENTS.md,且任何编码下都能解析。← 采用
另一个相关的坑:**PowerShell 5.1 向原生程序传参时会吃掉双引号**,
所以 `doctor.ps1` 里传给 `python -c` 的探测代码只能用单引号字符串。
---
## 九、已知的沙箱相关现象
如果在受限环境(文件系统只读 / 无网络)里跑 matplotlib,会看到:
```text
Matplotlib created a temporary cache directory at ... because there was an issue with
the default path (C:\Users\guzhu\.matplotlib)
```
这不是配置问题,是 `~\.matplotlib` 不可写。正常终端里不会出现;
真遇到时设置 `MPLCONFIGDIR` 指向可写目录即可:
```powershell
$env:MPLCONFIGDIR = "$env:TEMP\matplotlib"
```
+137
View File
@@ -0,0 +1,137 @@
# Git 工作流与远程托管
> 本仓库目前是**本地仓库**,之后托管到你自己的 Gitea。
---
## 一、仓库约定
### 行尾
由 `.gitattributes` 强制:**仓库内一律 LF**,checkout 时不做自动转换。
原因:AGENTS.md 要求 shell 脚本 UTF-8 无 BOM + LF;而这个仓库会被队友在
Windows / WSL / Linux 上 clone。不固定行尾的话,每次提交都会出现
"整个文件都变了"的假 diff,review 时根本看不出真实改动。
例外:`*.bat` / `*.cmd` 保持 CRLF(cmd 解析需要)。
Windows 用户建议同时设:
```powershell
git config --global core.autocrlf false
git config --global core.eol lf
```
### 什么不入库
见 `.gitignore`。重点是三条:
| 忽略 | 为什么 |
|---|---|
| `.venv/`、`.conda/` | 环境可由 `scripts/setup-*.ps1` 重建,不该进版本库 |
| `problems/**/raw/`、`**/*.xlsx`、`problems/**/*.csv` | 赛题原始数据体积大、可重新下载;只提交**处理脚本和结论** |
| `figures/*.png`、`figures/*.pdf`、`tables/*.csv` | 中间产物。定稿的图请放进 `problems/<年份>_<题号>/figures/` 再提交 |
**判断标准:这个东西能不能由仓库里的其他文件重新生成?**
能 → 不入库。
---
## 二、日常提交
```powershell
git add -A
git commit -m "feat(stats): 实现 bootstrap_ci"
```
提交信息建议带模块前缀,方便以后回溯"这个函数是哪次加的":
| 前缀 | 用途 |
|---|---|
| `feat(<模块>)` | 新增函数 / 功能 |
| `fix(<模块>)` | 修 bug |
| `test(<模块>)` | 只改测试 |
| `docs` | 只改文档 |
| `env` | 环境、依赖、脚本 |
| `chore` | 其他杂项 |
提交前跑一遍:
```powershell
.\scripts\doctor.ps1
```
它会同时检查 venv 和 conda 两套环境并跑测试。
---
## 三、推到自己的 Gitea
### 1. 在 Gitea 上建空仓库
不要勾选"初始化仓库"(不要生成 README / .gitignore / LICENSE),
否则首次 push 会冲突。
### 2. 加远程并推送
```powershell
cd D:\MCM&ICM_Toolkit
# 换成你自己的 Gitea 地址
git remote add origin https://git.你的域名/用户名/mcm-icm-toolkit.git
# 如果 Gitea 用的是自签证书,先在浏览器里访问一次并信任,或:
# git config --global http.sslBackend schannel
git branch -M main
git push -u origin main
```
### 3. 用 SSH(推荐,免密)
```powershell
ssh-keygen -t ed25519 -C "guzhu@mcm-toolkit"
Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" | Set-Clipboard
# 粘贴到 Gitea → 设置 → SSH/GPG 密钥
git remote set-url origin git@git.你的域名:用户名/mcm-icm-toolkit.git
git push -u origin main
```
### 4. 如果不想公开
Gitea 建仓库时选 **Private**。队友通过
仓库设置 → 协作者 → 添加成员 授权。
---
## 四、给队友的说明
队友 clone 后只需要:
```powershell
git clone <你的仓库地址>
cd mcm-icm-toolkit
.\scripts\setup-venv.ps1 # 零下载,复用他们自己的 Anaconda base
.\scripts\doctor.ps1
```
`setup-venv.ps1` 用的是 `--system-site-packages`,所以他们本机得先有 Anaconda。
如果队友没有 Anaconda,让他走 `scripts/setup-conda.ps1`。
---
## 五、回滚
环境升级出问题时的回退手段:
```powershell
# conda 自己保留了 revision 历史
conda list -n base --revisions
conda install -n base --revision <N>
# 代码回滚
git log --oneline
git revert <commit>
```
+20
View File
@@ -0,0 +1,20 @@
# 文档索引
本目录是 MCM/ICM Toolkit 的需求、架构、方案与开发规范的**唯一落盘处**。
| 文档 | 内容 |
|---|---|
| [01-suggestion-review.md](01-suggestion-review.md) | 对 ChatGPT 建库建议的**审查与精简**:留什么、砍什么、补什么 |
| [02-architecture.md](02-architecture.md) | 精简后的包结构、边界划分、统一接口约定 |
| [03-roadmap.md](03-roadmap.md) | 功能清单分级(L0 / L1 / L2 / 不做)与开发纪律 |
| [04-environment.md](04-environment.md) | conda / venv 环境配置、镜像、离线约束 |
| [dev/function-card-template.md](dev/function-card-template.md) | S 档算法的「函数卡片」模板 |
## 阅读顺序
1. 先读 `01-suggestion-review.md` —— 它决定后面所有事。
2. 再读 `02-architecture.md` —— 它决定代码往哪写。
3. 开工前读 `03-roadmap.md` 的对应条目 —— 它决定这次做什么、做到什么程度。
4. 环境出问题看 `04-environment.md`。
> 项目级进度看根目录的 `STATUS.md`;本目录只放**不会频繁变**的内容。
+26
View File
@@ -0,0 +1,26 @@
# 算法卡片
S 档(自己实现的招牌算法)的**函数卡片**放这里,模板见
[`../dev/function-card-template.md`](../dev/function-card-template.md)。
计划要写的卡片(见 `../03-roadmap.md` §3.1,约 15 张):
| 卡片 | 对应函数 | 状态 |
|---|---|---|
| `detect_outliers.md` | `data.detect_outliers` | ⬜ |
| `bootstrap_ci.md` | `stats.bootstrap_ci` | ⬜ |
| `monte_carlo.md` | `solve.monte_carlo` | ⬜ |
| `monte_carlo_probability.md` | `solve.monte_carlo_probability` | ⬜ |
| `monte_carlo_integral.md` | `solve.monte_carlo_integral` | ⬜ |
| `dynamic_programming.md` | `solve.dynamic_programming` | ⬜ |
| `knapsack_dp.md` | `solve.knapsack_dp` | ⬜ |
| `simulated_annealing.md` | `solve.simulated_annealing` | ⬜ |
| `local_sensitivity.md` | `solve.local_sensitivity` | ⬜ |
| `sobol_analyze.md` | `solve.sobol_analyze` | ⬜ |
| `entropy_weights.md` | `decision.entropy_weights` | ⬜ |
| `topsis.md` | `decision.topsis` | ⬜ |
| `ahp_weights.md` | `decision.ahp_weights` | ⬜ |
| `gm11_fit_predict.md` | `decision.gm11_fit_predict` | ⬜ |
> 卡片是写给自己半年后看的,不是给评审看的。
> 如果一张卡片最后只复述了 docstring,说明这个函数其实是 A 档,应该降档删卡片。
+18
View File
@@ -0,0 +1,18 @@
# API 参考
**本目录不放手写 API 文档。** 手写的 API 文档一定会和代码脱节。
接口的权威来源是三个地方,按优先级:
1. **源码 docstring** —— 每个函数的数学含义、参数、返回值、Notes、示例都写在那里;
2. **`__all__`** —— 每个子包的公开 API 清单,`tests/test_api_surface.py` 会校验它和实际导出一致;
3. **`docs/02-architecture.md`** —— 跨函数的约定(`Result` 字段、RNG 规则、`fit/predict/score` 协议)。
需要生成站点时用 mkdocs + mkdocstrings(后者直接从 docstring 生成):
```powershell
pip install mkdocs mkdocstrings[python]
mkdocs serve
```
**本仓库不把 mkdocs 放进依赖**——它是可选的、和比赛无关的工具。
+137
View File
@@ -0,0 +1,137 @@
# S 档「函数卡片」模板
> **只给 S 档用**(约 15 个招牌算法,见 `docs/03-roadmap.md` §3.1)。
> 其余 A 档函数只要求 docstring 一句话 + 一条 pytest,不写卡片。
>
> 卡片写在哪:`docs/algorithms/<函数名>.md`。
> 为什么写:S 档是自己实现、以后会去改内部实现的算法,
> 卡片是"半年后还看得懂当时为什么这么写"的唯一保障。
---
## 卡片模板
```markdown
# <函数名>
| 项 | 内容 |
|---|---|
| 模块 | `mcm_toolkit.<包>.<模块>` |
| 档位 | S |
| 参考来源 | <教材 / 论文 / 库;自己实现时参考了什么> |
## 1. 数学定义
<公式。用 LaTeX 写,例如 \`\\hat{\\theta}^* = \\arg\\min_\\theta ...\`。
一句话说清输入输出分别是什么数学对象。>
## 2. 适用场景
**用**:<什么题型、什么数据条件下该用它>
**不用**:<什么条件下它失效或不该用>
## 3. 参数与返回值
| 参数 | 类型 | 默认 | 含义 | 取值影响 |
|---|---|---|---|---|
| | | | | |
返回 `Result`:
| 字段 | 内容 |
|---|---|
| `params` | |
| `metrics` | |
| `data` | |
## 4. 复杂度
- 时间:`O(...)`
- 空间:`O(...)`
- **什么时候会跑不完**:<给出具体数字量级,例如"n > 1e6 时内存 8GB 打不住">
## 5. 数值稳定性
<浮点误差、除零、log(0)、上溢下溢、病态矩阵……
写清"什么输入会出问题"和"代码里怎么防的"。>
## 6. 常见错误
1. <错误用法> → <后果> → <正确做法>
2. …
## 7. 最小示例
```python
# 能直接复制进 notebook 跑通的 5~15 行
```
## 8. 单元测试要点
| 测什么 | 期望 |
|---|---|
| <已知解析解> | <精度要求> |
| <边界输入> | <行为> |
## 9. MCM/ICM 用法
- **哪类题**:<例如"评价类 / 预测类 / 优化类">
- **论文里怎么表述**:<一句话,能直接改写成论文的"方法"小节>
- **图**:<配哪张图,用哪个 `viz` 函数>
- **踩坑提醒**:<评审最可能质疑什么>
```
---
## 示例:`bootstrap_ci` 的卡片(示范,未定稿)
```markdown
# bootstrap_ci
| 项 | 内容 |
|---|---|
| 模块 | `mcm_toolkit.stats.bootstrap` |
| 档位 | S |
| 参考来源 | Efron (1979); Efron & Tibshirani (1993) *An Introduction to the Bootstrap* |
## 1. 数学定义
给定样本 \`x_1..x_n\` 与统计量 \`T(·)\`,从 \`x\` 中有放回抽 \`n\` 个得到 \`x^*\`,
重复 \`B\` 次得 \`T^*_1..T^*_B\`。百分位法区间为
\`[T^*_{(α/2·B)}, T^*_{(1-α/2)·B}]\`。
## 2. 适用场景
**用**:统计量没有解析分布(中位数、极差、自定义指标)、样本有偏、想给区间但不想假设正态。
**不用**:\`n < 10\`(经验分布不可靠);数据有强时间相关性时(要用 block bootstrap)。
## 3. 参数与返回值
| 参数 | 类型 | 默认 | 含义 | 取值影响 |
|---|---|---|---|---|
| `statistic` | callable | `np.mean` | 任意 `f(sample) -> float` | 换 `np.median` 就得到中位数的 CI |
| `n_resamples` | int | 10000 | 重采样次数 `B` | 越大区间端点越稳;10000 通常够 |
| `confidence` | float | 0.95 | 置信水平 | |
| `method` | str | `"percentile"` | `"percentile"` / `"bca"` | BCa 更准但慢 |
## 4. 复杂度
时间 \`O(B·n)\`;空间 \`O(B)\`(**按块处理,不落地 B×n 矩阵**)。
`n=1e6, B=1e4` 约 \`1e10\` 次操作 → **跑不完**,此时应改用解析近似或降 `B`。
## 7. 最小示例
\`\`\`python
import numpy as np
from mcm_toolkit.stats import bootstrap_ci
r = bootstrap_ci(heights, statistic=np.median, n_resamples=10000, random_state=42)
print(r.params["low"], r.params["high"])
\`\`\`
## 9. MCM/ICM 用法
- **哪类题**:任何要"给结论 + 给不确定性"的题,尤其评价类和预测类。
- **论文里怎么表述**:"采用非参数 bootstrap(B=10000,seed=42)估计中位数的 95% 置信区间。"
- **图**:预测曲线 + 阴影区间;用 `viz.plot`。
- **踩坑提醒**:必须报 `B` 和 seed;BCa 与 percentile 结果不同时要说明选了哪个及理由。
```
+18
View File
@@ -0,0 +1,18 @@
# 教程
按"题型"组织的实战教程放这里,每篇对应一类赛题。
**目前为空是刻意的**:教程应该在**做过至少一道该类型的真题之后**再写,
否则写出来的都是"字典式罗列",没有踩坑经验,没有价值。
计划(做完对应真题后再写):
| 教程 | 对应题型 | 触发条件 |
|---|---|---|
| `评价类.md` | 无量纲化 → 赋权 → TOPSIS/灰色 → 敏感性 | 做完一道评价类真题 |
| `预测类.md` | 描述 → 分布拟合 → ARIMA/GM(1,1) → 区间 → 误差分析 | 做完一道预测类真题 |
| `优化类.md` | 建模 → LP/IP → 启发式 → 多目标 → 敏感性 | 做完一道优化类真题 |
| `仿真类.md` | 参数分布 → Monte Carlo → 收敛性 → 鲁棒性 | 做完一道仿真类真题 |
面向"怎么用这个工具箱"的快速上手看 [`../04-environment.md`](../04-environment.md) 和
仓库根目录的 `README.md`;这里放的是**建模思路**。