98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Ti-2 配方优化问题建模 自检脚本(Issue #78)。
|
|
|
|
不依赖 unittest,直接加载模板资产并做能力点断言,便于 CI / 部署期一键核对。
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from problem import ConstraintKind, OptimizationProblem, load_problem # noqa: E402
|
|
from solver import SolverConfig, solve # noqa: E402
|
|
from cross_process import CrossProcessModel, CrossProcessModelConfig, CrossProcessSample # noqa: E402
|
|
from advisor import generate_advice # noqa: E402
|
|
|
|
CONFIG = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"config", "recipe_optim.template.yaml")
|
|
|
|
|
|
def main() -> int:
|
|
failures = []
|
|
|
|
# 1) 模板能加载
|
|
p = load_problem(CONFIG)
|
|
if not isinstance(p, OptimizationProblem):
|
|
failures.append("load_problem 未返回 OptimizationProblem")
|
|
|
|
# 2) 4 类约束齐全
|
|
kinds = {c.kind for c in p.constraints}
|
|
expected = {ConstraintKind.BOX, ConstraintKind.LINEAR,
|
|
ConstraintKind.RATIO, ConstraintKind.FORBIDDEN}
|
|
if kinds != expected:
|
|
failures.append(f"约束种类不齐: {kinds} != {expected}")
|
|
|
|
# 3) 静态校验通过
|
|
errs = p.validate()
|
|
if errs:
|
|
failures.append(f"validate 未通过: {errs}")
|
|
|
|
# 4) 可行性判定:合法取值可行、禁止组合不可行
|
|
ok = p.is_feasible({"clf_temp": 850, "cl2_ratio": 1.0,
|
|
"feed_rate": 450, "catalyst": "A"})
|
|
bad = p.is_feasible({"clf_temp": 950, "cl2_ratio": 1.0,
|
|
"feed_rate": 450, "catalyst": "A"})
|
|
if not ok:
|
|
failures.append("合法取值被判为不可行")
|
|
if bad:
|
|
failures.append("越界取值(950℃)未被识别为不可行")
|
|
|
|
# 5) 序列化往返无损
|
|
rt = OptimizationProblem.from_dict(p.to_dict())
|
|
if [v.name for v in rt.variables] != [v.name for v in p.variables]:
|
|
failures.append("序列化往返丢失变量")
|
|
|
|
# 6) 求解器(#79)端到端:加载模板后能求出可行解
|
|
sol = solve(p, SolverConfig(grid_steps=7, max_combinations=200000))
|
|
if not sol.feasible:
|
|
failures.append(f"求解器未求出可行解: {sol.message}")
|
|
if sol.strategy != "grid":
|
|
failures.append(f"求解策略非 grid: {sol.strategy}")
|
|
|
|
# 7) 跨工序关联模型(#80)端到端:合成线性数据训练 + R² 评估
|
|
cfg = CrossProcessModelConfig(
|
|
upstream_features=["up"], downstream_targets=["down"],
|
|
alpha=0.0, min_samples=8)
|
|
samples = [CrossProcessSample(upstream={"up": float(i)},
|
|
downstream={"down": 2.0 * float(i) + 1.0})
|
|
for i in range(12)]
|
|
cm = CrossProcessModel(cfg).fit(samples)
|
|
report = cm.evaluate(samples)
|
|
if not cm.fitted:
|
|
failures.append("跨工序模型未训练成功")
|
|
if not (report.get("r2_down", 0.0) > 0.99):
|
|
failures.append(f"跨工序模型 R² 过低: {report}")
|
|
|
|
# 8) 优化建议生成(#81)端到端:可解释、可溯源建议
|
|
advice = generate_advice(p, sol, cross_process_weights={
|
|
"Ti_purity": {"clf_temp": 0.8, "cl2_ratio": 1.2}})
|
|
if not advice.feasible:
|
|
failures.append("建议生成器标记不可行")
|
|
if len(advice.items) != len(p.variables):
|
|
failures.append("建议条目数与变量数不一致")
|
|
if not all(it.evidence for it in advice.items):
|
|
failures.append("存在无依据的建议条目(违反可溯源要求)")
|
|
if not advice.trace:
|
|
failures.append("溯源链路为空")
|
|
|
|
if failures:
|
|
print("❌ recipe-optim 自检失败:")
|
|
for f in failures:
|
|
print(" -", f)
|
|
return 1
|
|
print("✅ recipe-optim 自检通过(8 能力点)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|