"""优化。 分层(对应原建议的 A/B/C 三级): * **B 类(薄封装)**:`minimize` `linear_program` `integer_program` —— 底层 SciPy。 * **A 类(自己实现,S 档)**:`dynamic_programming` `knapsack_dp` `simulated_annealing`。 * **C 类(直接用库)**:NSGA-II 等,用 pymoo,本模块只提供 Pareto 前沿的**后处理**。 原建议里的 `maximize` 已删:它就是 `minimize` 取负,用 `sense="max"` 参数表达。 `value_iteration` / `policy_iteration` 也已删:与 `dynamic_programming` 同族, 第一版同时做三个属于过度设计。 """ from __future__ import annotations from collections.abc import Callable from typing import Any __all__ = [ "dynamic_programming", "integer_program", "knapsack_dp", "linear_program", "minimize", "pareto_front", "simulated_annealing", ] def minimize( objective: Callable[..., float], x0: Any = None, bounds: Any = None, constraints: Any = None, method: str = "auto", sense: str = "min", random_state: int | None = None, **kwargs: Any, ) -> Any: """连续优化(底层 `scipy.optimize.minimize`)。 Parameters ---------- method: `"auto"` 时按"有无约束 / 有无梯度"自动选: 无约束 → BFGS;有边界 → L-BFGS-B;有约束 → SLSQP。 sense: `"min"` / `"max"`。取 max 时内部对目标函数取负并还原结果, 调用方不用自己写负号(负号写错是这类题最常见的手滑)。 Returns ------- `Result`:`params={"x": 最优解}`,`metrics={"objective": 目标值}`。 Notes ----- **结果必须在论文里报"是否收敛"和"初值敏感性"**: 用一个 `x0` 跑出来的局部最优,和换 10 个随机初值跑出来的全局最优, 结论可能完全不同。返回值里带上 `success` 与 `message` 就是为了这个。 Examples -------- >>> f = lambda v: (v[0] - 3) ** 2 + (v[1] + 1) ** 2 >>> r = minimize(f, x0=[0, 0]) >>> round(r.metrics["objective"], 6) 0.0 """ # TODO(L1) raise NotImplementedError def linear_program( c: Any, A_ub: Any = None, b_ub: Any = None, A_eq: Any = None, b_eq: Any = None, bounds: Any = None, sense: str = "min", ) -> Any: """线性规划(底层 `scipy.optimize.linprog`)。 Parameters ---------- c: 目标函数系数。`sense="max"` 时内部取负。 bounds: `None` 表示默认 `x >= 0`;要无下界显式写 `(None, None)`。 **这是最容易踩的坑**:忘记声明变量非负,解出来的方案可能物理上不可行。 Examples -------- >>> r = linear_program(c=[1, 2], A_ub=[[1, 1]], b_ub=[4], bounds=[(0, None)] * 2) >>> r.metrics["objective"] 4.0 """ # TODO(L1) raise NotImplementedError def integer_program( c: Any, integrality: Any, A_ub: Any = None, b_ub: Any = None, A_eq: Any = None, b_eq: Any = None, bounds: Any = None, sense: str = "min", ) -> Any: """整数规划(底层 `scipy.optimize.milp`)。 `integrality`:0 = 连续,1 = 整数,2 = 0-1 变量。 Notes ----- 整数规划比 LP 慢得多。变量数到几百以上时先想想能不能用 线性松弛 + 取整 + 邻域搜索近似,别硬等求解器。 """ # TODO(L1) raise NotImplementedError def dynamic_programming( states: Any, actions: Any, transition: Callable[..., Any], reward: Callable[..., float], horizon: int, discount: float = 1.0, ) -> Any: """通用有限期动态规划(A 类,自己实现)。 Parameters ---------- transition: `f(state, action, rng) -> next_state`。 reward: `f(state, action, next_state) -> float`。 horizon: 期数。`discount < 1` 时按 `γ^t` 折算。 Returns ------- `Result`:`data` 是"最优值函数表 + 最优策略表",`extras["policy"]` 可直接查。 Notes ----- 复杂度 `O(horizon × |S| × |A|)`。**状态空间是唯一瓶颈**—— 如果 `|S| × holiday` 超过内存,要么精简状态离散化粒度,要么改成近似 DP。 Examples -------- >>> # 背包问题用 knapsack_dp 更直接;本函数用于"逐期决策"类题 >>> r = dynamic_programming(states, actions, trans, rew, horizon=10) # doctest: +SKIP """ # TODO(L1) ★ S 档:配完整函数卡片 raise NotImplementedError def knapsack_dp(weights: Any, values: Any, capacity: float) -> Any: """0-1 背包(动态规划模板,A 类,自己实现)。 复杂度 `O(n × capacity)`。`capacity` 在题面里是小数时先做单位缩放取整, 否则 DP 表会大到装不下。 Returns ------- `Result`:`params={"selected": 选中的下标}`,`metrics={"total_value", "total_weight"}`。 Examples -------- >>> r = knapsack_dp(weights=[1, 3, 4, 5], values=[1, 4, 5, 7], capacity=7) >>> r.metrics["total_value"] 9.0 """ # TODO(L1) ★ S 档 raise NotImplementedError def simulated_annealing( objective: Callable[..., float], x0: Any, bounds: Any = None, temp0: float = 1.0, cooling: float = 0.95, n_iter: int = 1000, sense: str = "min", random_state: int | None = None, **kwargs: Any, ) -> Any: """模拟退火(A 类,自己实现)。 Parameters ---------- cooling: 降温系数,每步 `T *= cooling`。`0.95` 配 `n_iter=1000` 是保守默认值。 temp0: 初始温度。**经验做法:取"随机扰动的典型劣化幅度"同量级**, 太大等于随机游走,太小等于爬山。 Returns ------- `Result`:`extras["history"]` 是每步的最优值轨迹,可直接喂给 `viz.plot_convergence`。 Notes ----- * 必须记录收敛曲线并画出来——论文里"算法收敛性分析"那一节就靠它。 * 结果对随机种子敏感,**至少跑 10 个种子**,报最好值 + 分布, 只报一个种子的结果是不可信的。 Examples -------- >>> r = simulated_annealing(lambda v: sum(x * x for x in v), x0=[3.0, 4.0], random_state=0) # doctest: +SKIP """ # TODO(L1) ★ S 档:配完整函数卡片 raise NotImplementedError def pareto_front(points: Any, sense: str = "min") -> Any: """从解集里筛出 Pareto 非支配解。 Returns ------- `Result`:`params={"mask": 布尔掩码, "rank": 每个解的支配层}`。 Notes ----- 多目标问题的**后处理**入口。真正的 NSGA-II 求解器用 pymoo (`pip install "mcm-toolkit[moo]"`),本函数只负责"给定一组解,挑出前沿"。 它也是画 Pareto 前沿图的必要前置步骤。 """ # TODO(L1) raise NotImplementedError