aps-agent/tests/golden/test_monte_carlo_config.py

108 lines
5.1 KiB
Python
Raw Permalink 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.

# ============================================================
# 蒙特卡洛可配置分布/相关性/置信区间黄金测试(plan.md §9.9 / 矩阵 88 行剩余项)
# 覆盖:分布可配置(normal/uniform/triangular)、需求-效率相关度、
# 置信区间输出、默认行为与种子确定性兼容。
# ============================================================
from __future__ import annotations
from server.aps_domain.robustness import run_monte_carlo
from server.state.seed import seed_world
def _demo_world() -> dict:
"""种子世界(RULE 引擎可排产,种子确定性)。"""
return seed_world()
def test_default_behavior_stable_and_ci_present():
"""默认参数:输出含既有字段 + 新增均值/标准差/置信区间/配置元信息。"""
report = run_monte_carlo(_demo_world(), trials=12, seed=2026)
dist = report["distribution"]
assert report["method"] == "fixed-seed-monte-carlo"
assert dist["tardinessP50"] <= dist["tardinessP90"] <= dist["tardinessP95"] <= dist["tardinessMax"]
assert "tardinessMean" in dist and "tardinessStd" in dist
lo, hi = dist["tardinessCI"]
assert lo <= dist["tardinessMean"] <= hi
assert dist["confidenceLevel"] == 0.95
cfg = report["config"]
assert cfg["correlation"] == 0.0
assert cfg["distributions"]["quantity"]["dist"] == "normal"
assert cfg["distributions"]["shockProbability"] == 0.15
assert "outcomes" in report
def test_seed_determinism_preserved_with_defaults():
"""默认配置下同种子输出完全一致(既有测试语义兼容)。"""
a = run_monte_carlo(_demo_world(), trials=8, seed=1234)
b = run_monte_carlo(_demo_world(), trials=8, seed=1234)
assert a["distribution"] == b["distribution"]
assert a["outcomes"] == b["outcomes"]
def test_uniform_distribution_bounds_and_determinism():
"""可配置 uniform 分布:样本都在 [low, high] 内且种子确定。"""
dist_cfg = {
"quantity": {"dist": "uniform", "low": 0.90, "high": 1.10},
"efficiency": {"dist": "uniform", "low": 0.95, "high": 1.05},
"shockProbability": 0.0,
}
a = run_monte_carlo(_demo_world(), trials=16, seed=7, distributions=dist_cfg)
b = run_monte_carlo(_demo_world(), trials=16, seed=7, distributions=dist_cfg)
assert a["outcomes"] == b["outcomes"]
for outcome in a["outcomes"]:
assert 0.90 <= outcome["quantityScale"] <= 1.10
assert 0.95 <= outcome["efficiencyScale"] <= 1.05
assert a["config"]["distributions"]["quantity"]["dist"] == "uniform"
def test_triangular_distribution_mode():
"""可配置 triangular 分布:样本在区间内且众数附近密度高(粗验)。"""
dist_cfg = {
"quantity": {"dist": "triangular", "low": 0.80, "high": 1.20, "mode": 1.0},
"shockProbability": 0.0,
}
report = run_monte_carlo(_demo_world(), trials=24, seed=99, distributions=dist_cfg)
scales = [o["quantityScale"] for o in report["outcomes"]]
assert all(0.80 <= s <= 1.20 for s in scales)
near_mode = sum(1 for s in scales if 0.95 <= s <= 1.05)
assert near_mode >= len(scales) * 0.4
def test_positive_correlation_moves_scales_together():
"""相关度 rho>0:quantity 与 efficiency 扰动同向(样本相关性为正)。"""
dist_cfg = {"shockProbability": 0.0}
report = run_monte_carlo(_demo_world(), trials=48, seed=42,
distributions=dist_cfg, correlation=0.8)
qs = [o["quantityScale"] for o in report["outcomes"]]
es = [o["efficiencyScale"] for o in report["outcomes"]]
mean_q, mean_e = sum(qs) / len(qs), sum(es) / len(es)
cov = sum((q - mean_q) * (e - mean_e) for q, e in zip(qs, es)) / len(qs)
std_q = (sum((q - mean_q) ** 2 for q in qs) / len(qs)) ** 0.5
std_e = (sum((e - mean_e) ** 2 for e in es) / len(es)) ** 0.5
sample_rho = cov / (std_q * std_e) if std_q > 0 and std_e > 0 else 0.0
assert sample_rho > 0.3, f"期望正相关,实际 {sample_rho:.3f}"
assert report["config"]["correlation"] == 0.8
def test_zero_correlation_keeps_independence():
"""相关度 rho=0:样本相关性接近 0(默认独立性语义不变)。"""
dist_cfg = {"shockProbability": 0.0}
report = run_monte_carlo(_demo_world(), trials=48, seed=42,
distributions=dist_cfg, correlation=0.0)
qs = [o["quantityScale"] for o in report["outcomes"]]
es = [o["efficiencyScale"] for o in report["outcomes"]]
mean_q, mean_e = sum(qs) / len(qs), sum(es) / len(es)
cov = sum((q - mean_q) * (e - mean_e) for q, e in zip(qs, es)) / len(qs)
std_q = (sum((q - mean_q) ** 2 for q in qs) / len(qs)) ** 0.5
std_e = (sum((e - mean_e) ** 2 for e in es) / len(es)) ** 0.5
sample_rho = cov / (std_q * std_e) if std_q > 0 and std_e > 0 else 0.0
assert abs(sample_rho) < 0.3
def test_shock_probability_configurable():
"""shockProbability=0 时无产能冲击事件。"""
dist_cfg = {"shockProbability": 0.0}
report = run_monte_carlo(_demo_world(), trials=12, seed=5, distributions=dist_cfg)
assert all(o["capacityShockLineId"] is None for o in report["outcomes"])
assert report["config"]["distributions"]["shockProbability"] == 0.0