# -*- 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())