fix(solve): 同步第三轮入口口径(docstring + 测试 + lint)
用户第三轮改了 5 处口径:非负校验带容差、initial 收 np.integer、paths 赋值挪进 try、
metrics 换成 terminal_distribution、mm1 放开 lambda=0 并收 numpy 标量。三处算法本体仍正确
(探针复测:终点分布 {0: 0.6644, 1: 0.3356}、lambda=0 给 L=Lq=Wq=0 且 W=1/mu、data is metrics 已为 False)。
Changed(Codex 只做同步,未改口径)
- docstring:markov_simulate 的 Returns 与"遗留"段、markov_steady_state 的 Raises
(TypeError / ValueError 分工)、mm1_metrics 的 Parameters / Returns / Raises。
- tests/test_solve.py:同步 5 条受影响的测试,新增 2 条(terminal_distribution 只列出现过的状态、
lambda=0 的空系统)。test_solve.py 64 条。
- ruff:B905(zip 缺 strict)+ 2 条空白 -> 0;rng 之后的双空行 -> 1。
- STATUS.md:决策 56 / 57 / 58 更新到"只剩 5 条小口径";「下一步」第八轮;
新增「第八轮备忘:models 与 decision 各是干什么的」(函数清单 / 使用顺序 / 建议开工顺序)。
- CHANGELOG 与 docs/dev/personal-pitfalls.md 易错点 30(口径一改,docstring / 测试 / STATUS 同时过期)。
校验:pytest 204 passed / 28 skipped(232 收集);ruff 0;pyright 0
This commit is contained in:
1 parent
953e988983
commit
9cb295a362
5 files changed
+190
-107
No files matched your search
+36
-9
@@ -428,7 +428,7 @@ def test_markov_simulate_absorbing_states_never_move():
|
||||
assert r.success
|
||||
assert np.all(r.data == 1)
|
||||
assert r.metrics["mean_terminal"] == 1.0
|
||||
assert r.metrics["std_terminal"] == 0.0
|
||||
assert r.metrics["terminal_distribution"] == {1: 1.0}
|
||||
|
||||
|
||||
def test_markov_simulate_is_reproducible():
|
||||
@@ -453,12 +453,25 @@ def test_markov_simulate_terminal_frequency_matches_stationary():
|
||||
|
||||
|
||||
def test_markov_simulate_metrics_are_terminal_states():
|
||||
"""`metrics` 只看终点:均值 + `{状态: 频率}`(频率之和为 1、与逐条统计一致)。"""
|
||||
from mcm_toolkit.solve import markov_simulate
|
||||
|
||||
P = np.array([[0.9, 0.1], [0.2, 0.8]])
|
||||
r = markov_simulate(P, initial=0, steps=50, n_paths=500, random_state=3)
|
||||
assert r.metrics["mean_terminal"] == float(np.mean(r.data[:, -1]))
|
||||
assert r.metrics["std_terminal"] == float(np.std(r.data[:, -1]))
|
||||
terminal = r.data[:, -1]
|
||||
dist = r.metrics["terminal_distribution"]
|
||||
assert r.metrics["mean_terminal"] == float(np.mean(terminal))
|
||||
assert np.isclose(sum(dist.values()), 1.0)
|
||||
for state, freq in dist.items():
|
||||
assert np.isclose(freq, float(np.mean(terminal == state)))
|
||||
|
||||
|
||||
def test_markov_simulate_terminal_distribution_only_lists_seen_states():
|
||||
"""只含实际出现过的状态(没出现的状态没有 key)—— docstring 已写明这条口径。"""
|
||||
from mcm_toolkit.solve import markov_simulate
|
||||
|
||||
r = markov_simulate(np.eye(3), initial=2, steps=5, n_paths=4, random_state=0)
|
||||
assert r.metrics["terminal_distribution"] == {2: 1.0}
|
||||
|
||||
|
||||
def test_markov_simulate_rejects_bad_transition():
|
||||
@@ -568,8 +581,8 @@ def test_markov_steady_state_accepts_numpy_integer():
|
||||
def test_markov_steady_state_rejects_bad_transition():
|
||||
from mcm_toolkit.solve import markov_steady_state
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
markov_steady_state(np.ones((2, 3)) / 3)
|
||||
with pytest.raises(TypeError):
|
||||
markov_steady_state(np.ones((2, 3)) / 3) # 形状不对 → TypeError(口径见决策 57)
|
||||
with pytest.raises(ValueError):
|
||||
markov_steady_state(np.array([[0.5, 0.4], [0.2, 0.8]]))
|
||||
with pytest.raises(ValueError):
|
||||
@@ -650,19 +663,33 @@ def test_mm1_rejects_unstable_system():
|
||||
equal = mm1_metrics(arrival_rate=3, service_rate=3)
|
||||
assert equal.success is False
|
||||
assert "不稳定" in equal.message
|
||||
assert equal.metrics == {}
|
||||
assert equal.metrics == {"rho": 1.0} # 失败时也把 rho 报出来(决策 58 的 ④)
|
||||
assert equal.data is None
|
||||
|
||||
assert mm1_metrics(arrival_rate=3, service_rate=2).success is False
|
||||
|
||||
|
||||
def test_mm1_allows_zero_arrival_rate_as_empty_system():
|
||||
"""λ = 0 是合法退化解:空系统(L = Lq = Wq = 0,W 取极限值 1/μ)。"""
|
||||
from mcm_toolkit.solve import mm1_metrics
|
||||
|
||||
r = mm1_metrics(arrival_rate=0, service_rate=3)
|
||||
assert r.success
|
||||
assert r.metrics["rho"] == 0.0
|
||||
assert r.metrics["L"] == 0.0
|
||||
assert r.metrics["Lq"] == 0.0
|
||||
assert r.metrics["Wq"] == 0.0
|
||||
assert np.isclose(r.metrics["W"], 1 / 3)
|
||||
|
||||
|
||||
def test_mm1_rejects_bad_arguments():
|
||||
"""λ / μ 必须是正数:0、负数、bool、非数值都要在入口拦下。"""
|
||||
"""λ 非负、μ 必须为正;bool 与非数值都要在入口拦下。"""
|
||||
from mcm_toolkit.solve import mm1_metrics
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
mm1_metrics(arrival_rate=0, service_rate=3)
|
||||
with pytest.raises(ValueError):
|
||||
mm1_metrics(arrival_rate=-1, service_rate=3)
|
||||
with pytest.raises(ValueError):
|
||||
mm1_metrics(arrival_rate=2, service_rate=0)
|
||||
with pytest.raises(TypeError):
|
||||
mm1_metrics(arrival_rate=True, service_rate=3)
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
Reference in new issue
Block a user