审查并精简 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 后端)
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
"""TOPSIS 逼近理想解排序。
|
||
|
||
原建议把标准化(`benefit_normalize` 等 4 个函数)单列一节,
|
||
这里合并进 `topsis` 的 `method` 参数——因为它们是同一个流程的步骤,
|
||
拆开反而容易出现"标准化用了一套、权重算的时候又换一套"的错位。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
__all__ = ["topsis"]
|
||
|
||
|
||
def topsis(
|
||
X: Any,
|
||
weights: Any = None,
|
||
benefit_columns: Any = None,
|
||
method: str = "vector",
|
||
labels: Any = None,
|
||
) -> Any:
|
||
"""TOPSIS 综合评价。
|
||
|
||
Parameters
|
||
----------
|
||
X:
|
||
决策矩阵,行 = 待评价方案,列 = 指标。
|
||
weights:
|
||
权重。`None` 时用等权(并在 `message` 里提示)。
|
||
benefit_columns:
|
||
效益型指标的列名/下标,其余按成本型处理。
|
||
**必填或显式全选**,不允许默认假设——方向搞反会让排名完全颠倒。
|
||
method:
|
||
列向量的无量纲化方式:`"vector"`(`x/√Σx²`,TOPSIS 原始做法,默认)
|
||
或 `"minmax"`。
|
||
labels:
|
||
方案名,用于结果表。
|
||
|
||
Returns
|
||
-------
|
||
`Result`:`data` 是含 `D+ / D- / closeness / rank` 四列、按 `rank` 排好的表,
|
||
`params["weights"]` 回显实际使用的权重。
|
||
|
||
Notes
|
||
-----
|
||
* 正理想解 = 各指标最优值,负理想解 = 各指标最劣值;
|
||
贴近度 `C = D- / (D+ + D-)`,**C 越大越好**。
|
||
* TOPSIS 的排名对权重敏感。论文里应附"权重扰动 ±10% 后排名是否稳定"的
|
||
敏感性分析——直接调 `solve.robustness_test`。
|
||
|
||
Examples
|
||
--------
|
||
>>> import numpy as np
|
||
>>> r = topsis(np.array([[1., 2.], [3., 1.], [2., 3.]]), benefit_columns=[0, 1])
|
||
>>> list(r.data["rank"])
|
||
[2, 3, 1]
|
||
"""
|
||
# TODO(L1) ★ S 档:配完整函数卡片
|
||
raise NotImplementedError
|