Files
MCM-ICM_Toolkit/docs/02-architecture.md
T
guzhujushi 5319589dfd 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 后端)
2026-10-01 17:43:48 +08:00

179 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构设计
> 上游依据:`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`。
- 每条断言都要能说清"错了说明什么"。