fix(solve): 审查 mm1_metrics + 第二批入口改动(工程侧收口 + 6 条测试)
用户本轮改了 markov_simulate / markov_steady_state 的入口校验,并写完 mm1_metrics,
三处算法本体都正确(mm1 的 5 个公式经探针与闭式解逐位对拍通过,含 W = Wq + 1/mu)。
Codex 只动工程侧:
Fixed
- mm1_metrics 的稳定性分界 "<" 应为 "<="(docstring 与既有测试的口径都是 lambda >= mu 不稳定):
lambda == mu 原先掉进 try、rho == 1.0 触发 ZeroDivisionError,被 except 吞成 success=False。
两个分支的可观测输出完全相同(success=False / metrics={} / data=None),只有 message 不同,
故按决策 31 直接修并加回归测试,未改动任何口径。
- 三处报错文案的参数名抄错:markov_steady_state 的 max_iter <= 0 写成 "tol 必须是正数",
mm1_metrics 的 service_rate 两条报错都写成 "tol";半角逗号统一为全角。
Changed
- mm1_metrics 的 docstring 补 Raises 与 data 说明;call_params = {...} 补空格;
ruff 6 条(W293 x5 / F541 f-string 无占位符 / W292 文件末尾无换行)。
- markov_steady_state 的 try 前 3 个空行 -> 1;"异常: " -> "异常:"。
- tests/test_solve.py:摘掉 2 条 mm1_metrics 的 skip,新增 4 条(lambda == mu 的回归、
非负校验回归、max_iter=3 未收敛判 success=False)。
- STATUS.md 决策 56 / 57 更新办结项 + 新增决策 58(mm1 的 4 条待拍板);CHANGELOG;易错点 29。
校验:pytest 202 passed / 28 skipped(230 收集);ruff 0;pyright 0
This commit is contained in:
1 parent
0955890010
commit
953e988983
5 files changed
+311
-72
No files matched your search
+65
-7
@@ -469,6 +469,8 @@ def test_markov_simulate_rejects_bad_transition():
|
||||
markov_simulate(np.array([[0.5, 0.5, 0.0], [0.2, 0.8, 0.0]]), initial=0)
|
||||
with pytest.raises(ValueError):
|
||||
markov_simulate(np.array([[0.5, 0.4], [0.2, 0.8]]), initial=0)
|
||||
with pytest.raises(ValueError):
|
||||
markov_simulate(np.array([[1.5, -0.5], [0.0, 1.0]]), initial=0) # 行和是 1,但含负元素
|
||||
|
||||
|
||||
def test_markov_simulate_rejects_bad_arguments():
|
||||
@@ -570,6 +572,20 @@ def test_markov_steady_state_rejects_bad_transition():
|
||||
markov_steady_state(np.ones((2, 3)) / 3)
|
||||
with pytest.raises(ValueError):
|
||||
markov_steady_state(np.array([[0.5, 0.4], [0.2, 0.8]]))
|
||||
with pytest.raises(ValueError):
|
||||
markov_steady_state(np.array([[1.5, -0.5], [0.0, 1.0]])) # 行和是 1,但含负元素
|
||||
|
||||
|
||||
def test_markov_steady_state_non_convergence_is_success_false():
|
||||
"""`max_iter` 不够时按"未收敛"处理:`success=False` 且 `converged=False`(口径 B)。"""
|
||||
from mcm_toolkit.solve import markov_steady_state
|
||||
|
||||
P = np.array([[0.9, 0.1], [0.2, 0.8]])
|
||||
r = markov_steady_state(P, max_iter=3)
|
||||
assert r.success is False
|
||||
assert r.metrics["converged"] is False
|
||||
assert r.metrics["iterations"] == 3
|
||||
assert "未在最大迭代次数内收敛" in r.message
|
||||
|
||||
|
||||
def test_markov_steady_state_rejects_bad_arguments():
|
||||
@@ -610,21 +626,63 @@ def test_linear_program_basic():
|
||||
assert np.isclose(r.metrics["objective"], 4.0)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason=_NOT_IMPLEMENTED)
|
||||
def test_mm1_metrics_closed_form():
|
||||
"""与闭式公式逐项对拍(ρ = λ/μ,L = ρ/(1-ρ),Wq = ρ/(μ-λ),W = Wq + 1/μ)。"""
|
||||
from mcm_toolkit.solve import mm1_metrics
|
||||
|
||||
arrival_rate, service_rate = 2, 3
|
||||
rho = arrival_rate / service_rate
|
||||
r = mm1_metrics(arrival_rate=arrival_rate, service_rate=service_rate)
|
||||
|
||||
assert r.success
|
||||
assert np.isclose(r.metrics["rho"], rho, rtol=1e-12)
|
||||
assert np.isclose(r.metrics["L"], rho / (1 - rho), rtol=1e-12)
|
||||
assert np.isclose(r.metrics["Lq"], rho**2 / (1 - rho), rtol=1e-12)
|
||||
assert np.isclose(r.metrics["W"], 1 / (service_rate - arrival_rate), rtol=1e-12)
|
||||
assert np.isclose(r.metrics["Wq"], rho / (service_rate - arrival_rate), rtol=1e-12)
|
||||
assert np.isclose(r.metrics["W"], r.metrics["Wq"] + 1 / service_rate, rtol=1e-12)
|
||||
|
||||
|
||||
def test_mm1_rejects_unstable_system():
|
||||
"""λ >= μ 时系统不稳定,应 success=False 而不是返回 inf(λ == μ 也要走这一支)。"""
|
||||
from mcm_toolkit.solve import mm1_metrics
|
||||
|
||||
equal = mm1_metrics(arrival_rate=3, service_rate=3)
|
||||
assert equal.success is False
|
||||
assert "不稳定" in equal.message
|
||||
assert equal.metrics == {}
|
||||
|
||||
assert mm1_metrics(arrival_rate=3, service_rate=2).success is False
|
||||
|
||||
|
||||
def test_mm1_rejects_bad_arguments():
|
||||
"""λ / μ 必须是正数:0、负数、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(TypeError):
|
||||
mm1_metrics(arrival_rate=True, service_rate=3)
|
||||
with pytest.raises(TypeError):
|
||||
mm1_metrics(arrival_rate=2, service_rate="3") # type: ignore[arg-type] # 就是要传错
|
||||
|
||||
|
||||
def test_mm1_docstring_example_runs():
|
||||
from mcm_toolkit.solve import mm1_metrics
|
||||
|
||||
r = mm1_metrics(arrival_rate=2, service_rate=3)
|
||||
assert np.isclose(r.metrics["rho"], 2 / 3, rtol=1e-6)
|
||||
assert np.isclose(r.metrics["L"], 2.0, rtol=1e-6)
|
||||
assert round(r.metrics["rho"], 4) == 0.6667
|
||||
|
||||
|
||||
@pytest.mark.skip(reason=_NOT_IMPLEMENTED)
|
||||
def test_mm1_rejects_unstable_system():
|
||||
"""λ >= μ 时系统不稳定,应 success=False 而不是返回 inf。"""
|
||||
def test_mm1_stability_boundary_is_success_not_exception():
|
||||
"""λ == μ 不能靠 ZeroDivisionError 落到失败分支:必须是显式的"不稳定"口径。"""
|
||||
from mcm_toolkit.solve import mm1_metrics
|
||||
|
||||
assert mm1_metrics(arrival_rate=3, service_rate=3).success is False
|
||||
r = mm1_metrics(arrival_rate=5, service_rate=5)
|
||||
assert r.success is False
|
||||
assert "ZeroDivisionError" not in r.message
|
||||
|
||||
|
||||
@pytest.mark.skip(reason=_NOT_IMPLEMENTED)
|
||||
|
||||
Reference in new issue
Block a user