"""SciPy SLSQP 后端。
把原先嵌入 :class:`~e2m2e.algorithm.transfer.transfer_optimization.DROTRONLPOptimizer`
的 SciPy SLSQP 求解循环抽出为顶层函数 :func:`solve_with_scipy`,由
``DROTRONLPOptimizer.optimize`` 调用。SLSQP 是 DRO→RO 转移优化的默认求解器,
无需额外依赖,仅依赖 ``scipy>=1.10``。
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
from scipy.optimize import Bounds, minimize
from ...data.templates import ConvergenceState, FailureCause
from ..results import scipy_slsqp_status
from .config import TransferOptimizationResult
from .nlp_core import NLPOptimizationVariables
if TYPE_CHECKING:
from .transfer_optimization import DROTRONLPOptimizer
[文档]
def solve_with_scipy(
optimizer: DROTRONLPOptimizer,
*,
initial_guess: NLPOptimizationVariables | None = None,
alpha_range: tuple[float, float] | None = None,
transfer_time_range: tuple[float, float] | None = None,
t_ins_range: tuple[float, float] | None = None,
use_relaxed_velocity_constraint: bool | None = None,
velocity_angle_constraint: float | None = None,
verbose: bool | None = None,
) -> TransferOptimizationResult:
"""使用 SciPy SLSQP 求解 DRO→RO 转移 NLP。
数学形式与论文 Cui et al. (2025) Section III.B 一致:最小化
:math:`\\Delta v_1 + \\Delta v_2`,约束包括位置连续性、速度平行性
(或松弛为不等式)以及变量范围。
Args:
optimizer: 已设置 ``alpha_range`` / ``transfer_time_range`` / ``t_ins_range``
的 :class:`DROTRONLPOptimizer`。
initial_guess: 初始猜测 ``(α, T, t_ins)``;默认 ``(1, 10, 5)``。
alpha_range: 覆盖 ``optimizer.alpha_range``。
transfer_time_range: 覆盖 ``optimizer.transfer_time_range``。
t_ins_range: 覆盖 ``optimizer.t_ins_range``。
use_relaxed_velocity_constraint: 是否使用松弛速度约束;``None`` 时取构造配置。
velocity_angle_constraint: 松弛速度约束角度(弧度);``None`` 时取构造配置。
verbose: 是否打印迭代信息;``None`` 时取构造配置。
Returns:
:class:`TransferOptimizationResult`,包含优化详情与转移类型分类。
"""
# 1. 范围覆盖
if alpha_range is not None:
optimizer.alpha_range = alpha_range
if transfer_time_range is not None:
optimizer.transfer_time_range = transfer_time_range
if t_ins_range is not None:
optimizer.t_ins_range = t_ins_range
# 2. 默认值解析
if use_relaxed_velocity_constraint is None:
use_relaxed_velocity_constraint = optimizer._use_relaxed_velocity
if velocity_angle_constraint is None:
velocity_angle_constraint = optimizer.velocity_angle_tol
if verbose is None:
verbose = optimizer._verbose
# 3. 初始猜测
if initial_guess is None:
alpha0, T0, t_ins0 = 1.0, 10.0, 5.0
else:
alpha0 = initial_guess.alpha
T0 = initial_guess.transfer_time
t_ins0 = initial_guess.t_ins
y0 = np.array([alpha0, T0, t_ins0])
# 4. 开启缓存(同一变量序列的积分结果可复用)
optimizer.enable_cache(True)
# 5. verbose 输出
if verbose:
print("\n开始NLP优化:")
print(f" 初始猜测: α={alpha0:.4f}, T={T0:.4f}, t_ins={t_ins0:.4f}")
print(f" α范围: [{optimizer.alpha_range[0]}, {optimizer.alpha_range[1]}]")
print(f" T范围: [{optimizer.transfer_time_range[0]}, {optimizer.transfer_time_range[1]}]")
print(f" t_ins范围: [{optimizer.t_ins_range[0]}, {optimizer.t_ins_range[1]}]")
# 6. 构造约束
constraints = [{"type": "eq", "fun": optimizer.constraint_position}]
if use_relaxed_velocity_constraint:
cos_theta_max = np.cos(velocity_angle_constraint)
constraints.append(
{
"type": "ineq",
"fun": lambda y: cos_theta_max - optimizer._compute_cos_angle(y),
}
)
else:
constraints.append({"type": "eq", "fun": optimizer.constraint_velocity_parallel})
bounds = Bounds(
lb=[
optimizer.alpha_range[0],
optimizer.transfer_time_range[0],
optimizer.t_ins_range[0],
],
ub=[
optimizer.alpha_range[1],
optimizer.transfer_time_range[1],
optimizer.t_ins_range[1],
],
)
# 7. 进度回调
iteration_counter = [0]
def _scipy_callback(xk: np.ndarray) -> None:
iteration_counter[0] += 1
if optimizer._progress_callback is not None:
alpha_k, T_k, tins_k = float(xk[0]), float(xk[1]), float(xk[2])
obj_k = float(optimizer.objective_function(xk))
optimizer._progress_callback(iteration_counter[0], obj_k, alpha_k, T_k, tins_k)
# 8. 求解
try:
result = minimize(
optimizer.objective_function,
y0,
method="SLSQP",
bounds=bounds,
constraints=constraints,
options={"ftol": 1e-10, "maxiter": 1000, "disp": verbose},
callback=_scipy_callback,
)
status, cause = scipy_slsqp_status(bool(result.success), int(result.status))
message = result.message
final_y = result.x
except Exception as e:
status = ConvergenceState.FAILED
cause = FailureCause.BACKEND_FAILURE
message = f"优化失败: {str(e)}"
final_y = y0
# 9. 组装结果
opt_vars = NLPOptimizationVariables.from_array(final_y)
opt_result = optimizer._build_result(
opt_vars,
status,
cause,
message,
use_relaxed_velocity_constraint,
velocity_angle_constraint,
)
# 10. verbose 结果输出
if verbose:
print("\n优化结果:")
print(f" 状态: {opt_result.status.value}")
print(f" 消息: {opt_result.message}")
print(f" α={opt_result.departure_alpha:.6f}")
print(f" T={opt_result.transfer_time:.6f}")
print(f" t_ins={opt_result.t_ins:.6f}")
print(f" ΔV1={opt_result.delta_v1:.6f}")
print(f" ΔV2={opt_result.delta_v2:.6f}")
print(f" 总ΔV={opt_result.total_delta_v:.6f}")
return opt_result