79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
"""decision 包测试。"""
|
|||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
pytestmark = pytest.mark.skip(reason="未实现:见 docs/03-roadmap.md L1")
|
||
|
|
|
||
|
|
|
||
|
|
def test_topsis_ranks_dominated_alternative_last():
|
||
|
|
"""一个在所有指标上都最差的方案,TOPSIS 必须排最后。"""
|
||
|
|
from mcm_toolkit.decision import topsis
|
||
|
|
|
||
|
|
X = np.array(
|
||
|
|
[
|
||
|
|
[9.0, 8.0, 7.0], # 全面最优
|
||
|
|
[5.0, 5.0, 5.0], # 中等
|
||
|
|
[1.0, 1.0, 1.0], # 全面最差
|
||
|
|
]
|
||
|
|
)
|
||
|
|
r = topsis(X, benefit_columns=[0, 1, 2])
|
||
|
|
ranks = dict(zip(range(3), r.data["rank"], strict=False))
|
||
|
|
assert ranks[0] == 1
|
||
|
|
assert ranks[2] == 3
|
||
|
|
|
||
|
|
|
||
|
|
def test_topsis_direction_flips_result():
|
||
|
|
"""把某列从效益型改成成本型,排名必须变——方向不能静默搞错。"""
|
||
|
|
from mcm_toolkit.decision import topsis
|
||
|
|
|
||
|
|
X = np.array([[9.0, 1.0], [1.0, 9.0]])
|
||
|
|
a = topsis(X, benefit_columns=[0, 1])
|
||
|
|
b = topsis(X, benefit_columns=[0])
|
||
|
|
assert list(a.data["rank"]) != list(b.data["rank"])
|
||
|
|
|
||
|
|
|
||
|
|
def test_entropy_weights_sum_to_one_and_prefers_dispersed_column(rng):
|
||
|
|
from mcm_toolkit.decision import entropy_weights
|
||
|
|
|
||
|
|
X = np.column_stack([rng.normal(size=50), np.ones(50)]) # 第二列无差异
|
||
|
|
r = entropy_weights(X)
|
||
|
|
assert np.isclose(r.params["weights"].sum(), 1.0)
|
||
|
|
assert r.params["weights"][0] > r.params["weights"][1]
|
||
|
|
|
||
|
|
|
||
|
|
def test_entropy_weights_handles_zero_and_negative():
|
||
|
|
"""熵权法的三个坑之一:零值不能产生 NaN。"""
|
||
|
|
from mcm_toolkit.decision import entropy_weights
|
||
|
|
|
||
|
|
r = entropy_weights(np.array([[0.0, 1.0], [1.0, 2.0], [2.0, 0.0]]))
|
||
|
|
assert np.isfinite(r.params["weights"]).all()
|
||
|
|
|
||
|
|
|
||
|
|
def test_ahp_consistency_for_consistent_matrix():
|
||
|
|
"""完全一致的判断矩阵 CR 应接近 0。"""
|
||
|
|
from mcm_toolkit.decision import ahp_weights
|
||
|
|
|
||
|
|
A = np.array([[1, 2, 4], [0.5, 1, 2], [0.25, 0.5, 1]], dtype=float)
|
||
|
|
r = ahp_weights(A)
|
||
|
|
assert r.metrics["CR"] < 0.01
|
||
|
|
|
||
|
|
|
||
|
|
def test_ahp_weights_match_geometric_mean_for_consistent_matrix():
|
||
|
|
from mcm_toolkit.decision import ahp_weights
|
||
|
|
|
||
|
|
A = np.array([[1, 2, 4], [0.5, 1, 2], [0.25, 0.5, 1]], dtype=float)
|
||
|
|
r = ahp_weights(A)
|
||
|
|
geo = (A.prod(axis=1)) ** (1 / 3)
|
||
|
|
assert np.allclose(r.params["weights"], geo / geo.sum(), atol=1e-3)
|
||
|
|
|
||
|
|
|
||
|
|
def test_gm11_fits_exponential_sequence():
|
||
|
|
"""GM(1,1) 对纯指数序列应拟合得极好。"""
|
||
|
|
from mcm_toolkit.decision import gm11_fit_predict
|
||
|
|
|
||
|
|
series = [2.0 * 1.1**k for k in range(6)]
|
||
|
|
r = gm11_fit_predict(series, steps=2)
|
||
|
|
assert r.metrics["c_ratio"] < 0.35 # 后验差一级
|